"Fossies" - the Fresh Open Source Software Archive

Member "relax-5.0.0/gui/about.py" (2 Dec 2019, 18328 Bytes) of package /linux/privat/relax-5.0.0.src.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "about.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.1.3_vs_5.0.0.

    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 = (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, (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 
  547     def draw_copyright(self):
  548         """Draw the copyright statements."""
  549 
  550         # Set the font.
  551         self.dc.SetFont(font.roman_normal)
  552 
  553         # The text extent.
  554         x1, y1 = self.dc.GetTextExtent(self.info.copyright[0])
  555         x2, y2 = self.dc.GetTextExtent(self.info.copyright[1])
  556 
  557         # Draw the text, with a starting spacer.
  558         self.dc.DrawText(self.info.copyright[0], (self.dim_x - x1)/2, self.offset(15))
  559         self.dc.DrawText(self.info.copyright[1], (self.dim_x - x2)/2, self.offset(y1+3))
  560 
  561         # Add the text extent.
  562         self.offset(y2)
  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, (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'), (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)