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