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