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