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