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