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

Source Code for Module gui.pipe_editor

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