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

Source Code for Module gui.about

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2009 Michael Bieri                                            # 
  4  # Copyright (C) 2010-2012 Edward d'Auvergne                                   # 
  5  #                                                                             # 
  6  # This file is part of the program relax (http://www.nmr-relax.com).          # 
  7  #                                                                             # 
  8  # This program is free software: you can redistribute it and/or modify        # 
  9  # it under the terms of the GNU General Public License as published by        # 
 10  # the Free Software Foundation, either version 3 of the License, or           # 
 11  # (at your option) any later version.                                         # 
 12  #                                                                             # 
 13  # This program is distributed in the hope that it will be useful,             # 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of              # 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               # 
 16  # GNU General Public License for more details.                                # 
 17  #                                                                             # 
 18  # You should have received a copy of the GNU General Public License           # 
 19  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
 20  #                                                                             # 
 21  ############################################################################### 
 22   
 23  # Python module imports. 
 24  from copy import deepcopy 
 25  from numpy import uint8, zeros 
 26  from os import sep 
 27  import webbrowser 
 28  import wx 
 29  import wx.html 
 30  from wx.lib.wordwrap import wordwrap 
 31   
 32  # relax module imports. 
 33  from graphics import IMAGE_PATH 
 34  from info import Info_box 
 35  from status import Status; status = Status() 
 36   
 37  # relax GUI module imports. 
 38  from gui.fonts import font 
 39  from gui.icons import relax_icons 
 40   
 41   
42 -class About_base(wx.Frame):
43 """The about dialog base class.""" 44 45 # The background colour (gradient if second colour is given). 46 colour1 = None 47 colour2 = None 48 49 # Dimensions. 50 dim_x = 400 51 dim_y = 600 52 max_x = None 53 max_y = None 54 55 # Spacer size (px). 56 border = 0 57 58 # Window styles. 59 style = wx.BORDER_NONE | wx.STAY_ON_TOP 60 61 # Destroy on clicking. 62 DESTROY_ON_CLICK = True 63 64 # Scrolling rate. 65 SCROLL_RATE = 20 66
67 - def __init__(self, parent=None, id=-1, title='', html_text=None):
68 """Build the dialog.""" 69 70 # Execute the base class __init__() method. 71 super(About_base, self).__init__(parent=parent, id=id, title=title, style=self.style) 72 73 # Set up the window icon. 74 self.SetIcons(relax_icons) 75 76 # Create a scrolled window. 77 self.window = wx.ScrolledWindow(self, -1) 78 79 # Initialise the y-offset variable. 80 self._offset_val = 0 81 82 # The starting cursor type. 83 self.cursor_type = 'normal' 84 85 # Initialise URL data structures. 86 self.url_text = [] 87 self.url_pos = [] 88 89 # Determine the virtual size of the window. 90 self.text_max_x = 0 91 self.virtual_size() 92 93 # Set the window size. 94 self.SetSize((self.virt_x, self.dim_y)) 95 96 # Add y scrolling, if needed. 97 self.window.SetScrollRate(0, self.SCROLL_RATE) 98 99 # Create the buffered device context twice (to determine the real virtual size!). 100 self.create_buffered_dc() 101 self.create_buffered_dc() 102 103 # Add HTML content. 104 if html_text: 105 self.add_html(html_text) 106 107 # Draw everything. 108 self.window.Bind(wx.EVT_PAINT, self.generate) 109 110 # Let the dialog be closable with a left button click. 111 self.window.Bind(wx.EVT_MOTION, self.cursor_style) 112 113 # Let the dialog be closable with a left button click. 114 self.window.Bind(wx.EVT_LEFT_DOWN, self.process_click) 115 116 # Center Window 117 self.Centre()
118 119
120 - def add_html(self, text):
121 """Add the given HTML text to the DC. 122 123 @param text: The HTML text. 124 @type text: str 125 """ 126 127 # The HTML renderer. 128 self.html = wx.html.HtmlDCRenderer() 129 130 # Set the font. 131 self.html.SetFonts("Roman", "Courier") 132 133 # Set the DC. 134 self.html.SetDC(self.dc, 1.0) 135 136 # Set the size of the HTML object. 137 self.html.SetSize(self.virt_x, self.virt_y) 138 139 # Add the text. 140 self.html.SetHtmlText(text) 141 142 # Render the HTML. 143 self.html.Render(self.border, self.border, known_pagebreaks=[])
144 145
146 - def build_widget(self):
147 """Dummy widget building method."""
148 149
150 - def create_buffered_dc(self):
151 """Build the buffered dc containing the window contents.""" 152 153 # The buffer for buffered drawing (work around for a GTK bug, the bitmap must be square!!!). 154 size = max(self.virt_x, self.virt_y) 155 self.buffer = wx.EmptyBitmap(size, size) 156 157 # Create the device context. 158 self.dc = wx.BufferedDC(None, self.buffer) 159 160 # Set a background. 161 self.set_background() 162 163 # Debugging lines. 164 if status.debug: 165 # Set the font. 166 self.dc.SetFont(wx.Font(8, wx.FONTFAMILY_SCRIPT, wx.NORMAL, wx.NORMAL)) 167 168 # The virtual size. 169 self.dc.DrawText("Virt size: %sx%s" % (self.virt_x, self.virt_y), 2, 2) 170 171 # Cross. 172 self.dc.DrawLine(0, 0, self.virt_x, self.virt_y) 173 self.dc.DrawLine(self.virt_x, 0, 0, self.virt_y) 174 175 # Lines every 100 pixels. 176 num = self.virt_y / 100 177 for i in range(num+1): 178 pos = i * 100 179 self.dc.DrawLine(0, pos, self.virt_x, pos) 180 self.dc.DrawText(str(pos), self.virt_x-40, pos-10) 181 182 # Build the rest of the about widget. 183 self.build_widget() 184 185 # Re-calculate the virtual size, and reset the offset. 186 self.virt_y = self.offset() 187 self.offset(-self.virt_y) 188 189 # Finish. 190 self.dc.EndDrawing()
191 192
193 - def cursor_style(self, event):
194 """Change the mouse cursor when over the url.""" 195 196 # Determine the mouse position. 197 x = event.GetX() 198 y = event.GetY() 199 200 # Scrolling. 201 y = y + self.window.GetViewStart()[1]*self.SCROLL_RATE 202 203 # Selection cursor. 204 over_url = False 205 for i in range(len(self.url_pos)): 206 if x > self.url_pos[i][0, 0] and x < self.url_pos[i][0, 1] and y > self.url_pos[i][1, 0] and y < self.url_pos[i][1, 1]: 207 over_url = True 208 209 # Only change if needed. 210 if over_url and self.cursor_type == 'normal': 211 # Build the cursor. 212 select_cursor = wx.StockCursor(wx.CURSOR_HAND) 213 214 # Set the cursor. 215 self.window.SetCursor(select_cursor) 216 217 # Reset the cursor type. 218 self.cursor_type = 'select' 219 220 # Normal cursor. 221 if not over_url and self.cursor_type == 'select': 222 # Build the cursor. 223 select_cursor = wx.StockCursor(wx.CURSOR_ARROW) 224 225 # Set the cursor. 226 self.window.SetCursor(select_cursor) 227 228 # Reset the cursor type. 229 self.cursor_type = 'normal'
230 231
232 - def draw_url(self, url_text=None, pos_x=0, carriage_ret=False, centre=False):
233 """Draw a URL as a hyperlink. 234 235 @keyword url_text: The text of the url. 236 @type url_text: str 237 @keyword pos_x: The starting x position for the text. 238 @type pos_x: int 239 @keyword carriage_ret: A flag which if True will cause a carriage return, by shifting the offset by y. 240 @type carriage_ret: bool 241 @keyword centre: A flag which if True will cause the URL to be centred in the window. 242 @type centre: bool 243 """ 244 245 # Get the original font. 246 orig_font = self.dc.GetFont() 247 orig_fg = deepcopy(self.dc.GetTextForeground()) 248 249 # Set the font. 250 self.dc.SetFont(font.roman_normal) 251 self.dc.SetTextForeground('#0017aa') 252 253 # The text extent. 254 x, y = self.dc.GetTextExtent(url_text) 255 256 # Draw the text centred. 257 if centre: 258 pos_x = (self.dim_x - x)/2 259 260 # Draw the text. 261 text = self.dc.DrawText(url_text, pos_x, self.offset()) 262 263 # Store the position of the text. 264 self.url_pos.append(zeros((2, 2), int)) 265 self.url_pos[-1][0] = [pos_x, pos_x + x] 266 self.url_pos[-1][1] = [self.offset(), self.offset()+y] 267 268 # Shift down. 269 if carriage_ret: 270 self.offset(y) 271 272 # Store the URL. 273 self.url_text.append(url_text) 274 275 # Restore the original font. 276 self.dc.SetFont(orig_font) 277 self.dc.SetTextForeground(orig_fg)
278 279
280 - def draw_title(self, text, alt_font=None):
281 """Draw the title. 282 283 @param text: The text of the title. 284 @type text: str 285 @keyword alt_font: An alternative font. 286 @type alt_font: wx.Font instance 287 """ 288 289 # Set the font. 290 if alt_font == None: 291 alt_font = font.roman_title 292 293 self.dc.SetFont(alt_font) 294 295 # The text extent. 296 x, y = self.dc.GetTextExtent(text) 297 298 # Draw the text, with a spacer. 299 self.dc.DrawText(text, (self.virt_x - x)/2, self.offset(15)) 300 301 # Add the text extent. 302 self.offset(y)
303 304
305 - def draw_wrapped_text(self, text, spacer=10):
306 """Generic method for drawing wrapped text in the relax about widget. 307 308 @param text: The text to wrap and draw. 309 @type text: str 310 @keyword spacer: The pixel width of the spacer to place above the text block. 311 @type spacer: int 312 """ 313 314 # Set the font. 315 self.dc.SetFont(font.roman_normal) 316 317 # Wrap the text. 318 width = self.dim_x - 2*self.border 319 wrapped_text = wordwrap(text, width, self.dc) 320 321 # Find the full extents. 322 full_x, full_y = self.dc.GetTextExtent(wrapped_text) 323 324 # Add a top spacer. 325 self.offset(10) 326 327 # Draw. 328 lines = wrapped_text.split('\n') 329 for line in lines: 330 # Find and break out the URLs from the text. 331 text_elements, url = self.split_refs(line) 332 333 # Draw the text. 334 pos_x = self.border 335 for i in range(len(text_elements)): 336 # URL text. 337 if url[i]: 338 self.draw_url(url_text=text_elements[i], pos_x=pos_x) 339 340 # Add the text. 341 else: 342 self.dc.DrawText(text_elements[i], pos_x, self.offset()) 343 344 # The new x position. 345 x, y = self.dc.GetTextExtent(text_elements[i]) 346 pos_x += x 347 348 # Update the offset. 349 self.offset(y + 1)
350 351
352 - def generate(self, event):
353 """Build the device context, add the background, and build the dialog. 354 355 @param event: The wx event. 356 @type event: wx event 357 """ 358 359 # Temporary fix for wxPython 2.9.3.1 suggested by Robin Dunn at http://groups.google.com/group/wxpython-users/browse_thread/thread/7dff3f5d7ca24985. 360 dc = wx.PaintDC(self.window) 361 self.window.PrepareDC(dc) 362 dc.DrawBitmap(self.buffer, 0, 0) 363 364 # Create the device context. 365 wx.BufferedPaintDC(self.window, self.buffer, wx.BUFFER_VIRTUAL_AREA)
366 367
368 - def offset(self, val=0):
369 """Shift the y-offset by the given value and return the new offset. 370 371 @keyword val: The value to add to the offset (can be negative). 372 @type val: int 373 @return: The new offset. 374 @rtype: int 375 """ 376 377 # Shift. 378 self._offset_val = self._offset_val + val 379 380 # Return. 381 return self._offset_val
382 383
384 - def process_click(self, event):
385 """Determine what to do with the mouse click. 386 387 @param event: The wx event. 388 @type event: wx event 389 """ 390 391 # Determine the mouse position. 392 x = event.GetX() 393 y = event.GetY() 394 395 # Scrolling. 396 y = y + self.window.GetViewStart()[1]*self.SCROLL_RATE 397 398 # A click on a URL. 399 for i in range(len(self.url_pos)): 400 if x > self.url_pos[i][0, 0] and x < self.url_pos[i][0, 1] and y > self.url_pos[i][1, 0] and y < self.url_pos[i][1, 1]: 401 webbrowser.open_new(self.url_text[i]) 402 403 # Close the widget. 404 if self.DESTROY_ON_CLICK: 405 self.Destroy()
406 407
408 - def set_background(self):
409 """Build a background for the dialog.""" 410 411 # Set a single colour. 412 if self.colour1 and not self.colour2: 413 self.SetBackgroundColour(self.colour1) 414 415 # A gradient. 416 elif self.colour1 and self.colour2: 417 self.dc.GradientFillLinear((0, 0, self.virt_x, self.virt_y), self.colour1, self.colour2, wx.SOUTH)
418 419
420 - def split_refs(self, text):
421 """Split up text based on the location of URLs. 422 423 @param text: The text to parse and split up. 424 @type text: str 425 @return: The list of text elements, and a list of flags which if True indicates a corresponding URL in the text list. 426 @rtype: list of str, list of bool 427 """ 428 429 # Init. 430 elements = [] 431 url = [] 432 433 # Walk over the characters. 434 for i in range(len(text)): 435 # End. 436 if len(text) - i < 7: 437 break 438 439 # Search for a url. 440 if text[i:i+7] == 'http://': 441 # Add the text up to here to the list. 442 elements.append(text[0:i]) 443 url.append(False) 444 445 # Find the end. 446 end_char = [')', ' '] 447 for j in range(i+7, len(text)): 448 if text[j] in end_char: 449 end_i = j 450 break 451 452 # The url. 453 elements.append(text[i:j]) 454 url.append(True) 455 456 # The rest of the text. 457 elements.append(text[j:]) 458 url.append(False) 459 460 # Terminate. 461 break 462 463 # No URLs. 464 if not len(elements): 465 elements.append(text) 466 url.append(False) 467 468 # Return the data structures. 469 return elements, url
470 471
472 - def virtual_size(self):
473 """Determine the virtual size of the window.""" 474 475 # Dimensions of the drawing area. 476 if self.max_x: 477 self.virt_x = self.max_x 478 else: 479 self.virt_x = self.dim_x 480 if self.max_y: 481 self.virt_y = self.max_y 482 else: 483 self.virt_y = self.dim_y
484 485 486
487 -class About_relax(About_base):
488 """The about relax dialog.""" 489 490 # The relax background colour. 491 colour1 = '#e5feff' 492 colour2 = '#88cbff' 493 494 # Dimensions. 495 dim_x = 450 496 dim_y = 700 497 498 # Spacer size (px). 499 border = 10 500
501 - def __init__(self, parent=None, id=-1, title="About relax"):
502 """Build the dialog.""" 503 504 # Initialise the program information container. 505 self.info = Info_box() 506 507 # Execute the base class __init__() method. 508 super(About_relax, self).__init__(parent=parent, id=id, title=title)
509 510
511 - def build_widget(self):
512 """Build the about dialog.""" 513 514 # A global Y offset for packing the elements together (initialise to the border position). 515 self.offset(self.border) 516 517 # Draw all the elements. 518 self.draw_title(self.info.title + ' ' + self.info.version) 519 self.draw_description() 520 self.draw_copyright() 521 self.offset(10) 522 self.draw_url(url_text=self.info.website, carriage_ret=True, centre=True) 523 self.draw_icon() 524 self.draw_desc_long() 525 self.draw_licence() 526 527 # Resize the window. 528 dim_x = self.dim_x 529 dim_y = self.offset() + self.border 530 self.SetSize((dim_x, dim_y)) 531 self.window.SetVirtualSize((dim_x, dim_y)) 532 self.window.EnableScrolling(x_scrolling=False, y_scrolling=False) 533 534 # Add space to the bottom. 535 self.offset(self.border)
536 537 554 555
556 - def draw_desc_long(self):
557 """Draw the long relax description.""" 558 559 self.draw_wrapped_text(self.info.desc_long, spacer=10)
560 561
562 - def draw_description(self):
563 """Draw the relax description text.""" 564 565 # Set the font. 566 self.dc.SetFont(font.roman_font_12) 567 568 # The text extent. 569 x, y = self.dc.GetTextExtent(self.info.desc) 570 571 # Draw the text, with a spacer. 572 self.dc.DrawText(self.info.desc, (self.dim_x - x)/2, self.offset(15)) 573 574 # Add the text extent. 575 self.offset(y)
576 577
578 - def draw_icon(self):
579 """Draw the relax icon on the canvas.""" 580 581 # Add the relax logo. 582 self.dc.DrawBitmap(wx.Bitmap(IMAGE_PATH+'ulysses_shadowless_400x168.png'), (self.dim_x - 400)/2, self.offset(20), True) 583 584 # Add the bitmap width to the offset. 585 self.offset(168)
586 587
588 - def draw_licence(self):
589 """Draw the relax licence text.""" 590 591 self.draw_wrapped_text(self.info.licence, spacer=10)
592