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

Source Code for Module gui.pipe_editor

  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 menu.AppendItem(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.SetToolTipString("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.SetToolTipString("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.SetToolTipString("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.SetToolTipString("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.SetToolTipString("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.SetToolTipString("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()
567