#!/usr/local/bin/python2.6
# coding=utf-8

'''
gnome-about

 # Pretty About Dialog for the GNOME Desktop #

Copyright (C) 2007 Guillaume Seguin <guillaume@segu.in>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.

Authors:
    Guillaume Seguin <guillaume@segu.in>
    Vincent Untz <vuntz@gnome.org> (get_language_names () helper function)
'''

import pygtk
pygtk.require ('2.0')

import gobject
from gobject.option import OptionParser, make_option
import gtk

import cairo
from math import pi

import os, sys, random, time, gettext, locale

import xml.dom.minidom

DESCRIPTION_DELAY = 10000
CONTRIBUTOR_DELAY = 2500

PACKAGE         = "gnome-desktop"
VERSION         = "2.32.1"
GETTEXT_PACKAGE = "gnome-desktop-2.0"

LOCALEDIR       = "/usr/local/share/locale"
DATADIR         = "/usr/local/share/gnome-about"
ICONDIR         = "/usr/local/share/pixmaps"

LOGO_FILE        = "gnome-64.png"

gettext.install (GETTEXT_PACKAGE, LOCALEDIR, unicode = True)

header_links = [
    (_("About GNOME"), "http://www.gnome.org/about/"),
    (_("News"), "http://news.gnome.org/"),
    (_("GNOME Library"), "http://library.gnome.org/"),
    (_("Friends of GNOME"), "http://www.gnome.org/friends/"),
    (_("Contact"), "http://www.gnome.org/contact/"),
]

translated_contributors = [
    _("The Mysterious GEGL"),
    _("The Squeaky Rubber GNOME"),
    _("Wanda The GNOME Fish")
]

default_link_color = gtk.gdk.Color (0, 0, 65535)

def locate_file (file):
    '''Wrap gnome_program_locate_file to avoid ugly duplication'''
    dirnames = [DATADIR, '.']
    for dirname in dirnames:
        filename = os.path.join(dirname, file)
        if os.path.exists(filename):
            return filename
    return False

def cleanup_date (date):
    '''Parse a date as found in gnome-version.xml and nicely format it'''
    try:
        # FIXME: we don't have g_locale_to_utf8 in python. See bug #530382
        return unicode (time.strftime ("%x", time.strptime (date, "%Y-%m-%d")), locale.getpreferredencoding ()).encode ("utf-8")
    except:
        return ""

# Imported from GNOME's Sabayon
# (sabayon/admin-tool/lockdown/disabledapplets.py)
# There's no wrapper for g_get_language_names (). Ugly workaround:
# Note that we don't handle locale alias...
def get_language_names ():
    if "LANGUAGE" in os.environ.keys () and os.environ["LANGUAGE"] != "":
        env_lang = os.environ["LANGUAGE"].split ()
    elif "LC_ALL" in os.environ.keys () and os.environ["LC_ALL"] != "":
        env_lang = os.environ["LC_ALL"].split ()
    elif "LC_MESSAGES" in os.environ.keys () and os.environ["LC_MESSAGES"] != "":
        env_lang = os.environ["LC_MESSAGES"].split ()
    elif "LANG" in os.environ.keys () and os.environ["LANG"] != "":
        env_lang = os.environ["LANG"].split ()
    else:
        env_lang = []

    env_lang.reverse ()
    languages = []

    for language in env_lang:
        start_pos = 0
        mask = 0
        uscore_pos = language.find ("_")
        if uscore_pos != -1:
            start_pos = uscore_pos
            mask += 1 << 2
        dot_pos = language.find (".", start_pos)
        if dot_pos != -1:
            start_pos = dot_pos
            mask += 1 << 0
        at_pos = language.find ("@", start_pos)
        if at_pos != -1:
            start_pos = at_pos
            mask += 1 << 1

        if uscore_pos != -1:
            lang = language[:uscore_pos]
        elif dot_pos != -1:
            lang = language[:dot_pos]
        elif at_pos != -1:
            lang = language[:at_pos]
        else:
            lang = language

        if uscore_pos != -1:
            if dot_pos != -1:
                territory = language[uscore_pos:dot_pos]
            elif at_pos != -1:
                territory = language[uscore_pos:at_pos]
            else:
                territory = language[uscore_pos:]
        else:
            territory = ""

        if dot_pos != -1:
            if at_pos != -1:
                codeset = language[dot_pos:at_pos]
            else:
                codeset = language[dot_pos:]
        else:
            codeset = ""

        if at_pos != -1:
            modifier = language[at_pos:]
        else:
            modifier = ""

        for i in range (mask + 1):
            if i & ~mask == 0:
                newlang = lang
                if (i & 1 << 2):
                    newlang += territory
                if (i & 1 << 0):
                    newlang += codeset
                if (i & 1 << 1):
                    newlang += modifier
                languages.insert (0, newlang)

    return languages

class GettableList (list):
    '''Dumb wrapper around Python list with a get () method to iterate \
the list'''

    current = 0

    def get (self):
        if not len (self):
            return None
        if self.current == -1:
            item = None
        else:
            item = self[self.current]
        self.current += 1
        if self.current == len (self):
            self.current = -1
        else:
            self.current = self.current % len (self)
        return item

class GnomeContributors (GettableList):
    '''Randomized contributors list'''

    current      = 0

    def __init__ (self):
        '''Initiate object and load contributors lists'''
        super (GnomeContributors, self).__init__ ()
        map (self.append, translated_contributors)
        self.load_from_file ("contributors.list")
        self.load_from_file ("foundation-members.list")
        random.shuffle (self) # Randomize list...

    def load_from_file (self, file):
        '''Load a list of contributors and validate it'''
        def validate_contributor (contrib):
            try:
                contrib.encode ("utf8")
                return len (contrib) > 0 and contrib[0] != "#"
            except Exception, e:
                print e
                return False

        path = locate_file (file)

        if not path:
            print '''Warning: "%s" file not found.''' % file
            return

        f = open (path, "r")
        try:
            data = f.readlines ()
        finally:
            f.close ()

        '''Cleanup list'''
        data = map (lambda s: s.rstrip (), data)

        '''Check that the file begins with the correct header'''
        if not data or data[0] != "# gnome-about contributors - format 1":
            return

        '''Filter the contributors list and append it'''
        contributors = filter (validate_contributor, data)
        map (self.append, contributors)

    def get (self):
        '''Return a contributor from the currently randomized list. \
           If we hit the end of the list, randomize it again'''
        contributors_count = len (self)
        if not contributors_count:
            print "Warning: empty contributors list."
            return
        contributor = self[self.current]
        self.current += 1
        if self.current >= contributors_count:
            '''Remove the last item of the list and reinsert it after \
               shuffling to make sure it won't get displayed twice in a row'''
            self.pop ()
            random.shuffle (self)
            index = random.randint (contributors_count / 2,
                                    contributors_count)
            self.insert (index, contributor)
            self.current = 0
        return contributor

# Animation is done using:
#  * A custom gtk.Label, for catching mouse press events
#  * A gtk.Alignment, for positionning and scrolling (the actual animation)
#  * A gtk.Layout, so that the Alignment can be wider than what's displayed
#    and thus let the Label appear and disappear smoothly

class AnimatedLabel (gtk.Layout):
    '''Pretty animated label'''

    items   = None # items must either be a GettableList object
                   # or expose a get () method'''
    timeout = 0
    format  = ""   # format must be a valid formatting string for a single %s

    item    = None
    next    = None

    current = None
    label   = None
    source  = None
    state   = 0    # 0 = appearing ; 1 = landed ; 2 = vanishing
    pos     = 0.0

    width   = 0
    height  = 0

    def __init__ (self, items, width, height, timeout, format = "%s"):
        '''Initiate object'''
        super (AnimatedLabel, self).__init__ ()
        self.items = items
        self.next = self.items.get () # Pop the first item
        self.width = width
        self.height = height
        self.timeout = timeout
        self.format = format
        self.set_size_request (width, height)
        self.connect ("button-press-event", self.on_button_press)
        self.connect ("map", self.reset_animation)

    def reset_animation (self, *args):
        '''Reset label and fire animation timer'''
        self.reset_label ()
        if not self.label:
            return
        self.source = gobject.timeout_add (5, self.animate)

    def make_label (self):
        '''Build the label widgets'''
        self.label = WindowedLabel ()
        self.label.connect ("button-press-event", self.on_button_press)
        self.label.set_justify (gtk.JUSTIFY_FILL)
        self.label.set_line_wrap (True)
        self.label.set_markup (self.format % self.item)
        self.label.set_selectable (True)

    def reset_label (self):
        '''Drop current label if any and create the new one'''
        if self.current:
            self.remove (self.current)
            self.current = None
            self.label = None
        self.item = self.next
        self.next = self.items.get ()
        if not self.item:
            return False
        self.make_label ()
        self.state = -1

    def on_button_press (self, widget, event):
        '''Switch to next item upon left click'''
        if event.button != 1 or not self.current:
            return
        # Remove the current timeout if any to avoid bad side effects
        if self.source:
            gobject.source_remove (self.source)
        self.animate ()
        return True

class VertAnimatedLabel (AnimatedLabel):
    '''Vertically animated label'''

    rewind_text       = ""
    last_label_height = 0

    def rewind_animate (self):
        '''Animation function for the rewind step'''
        self.source = None
        if self.state == -2:
            self.item = self.rewind_text
            self.make_label ()
            label_height = self.label.size_request ()[1]
            total_height = self.height + label_height
            self.pos = float (self.last_label_height) / total_height
            self.current.set (0.5, self.pos, 0, 0)
            self.state = 0
            self.source = gobject.timeout_add (10, self.rewind_animate)
        elif self.state == 0:
            if self.pos < 1.0:
                '''Move towards the bottom position'''
                self.pos = min (1.0, self.pos + 0.01)
                self.current.set (0.5, self.pos, 0, 0)
                self.source = gobject.timeout_add (10, self.rewind_animate)
            else:
                '''Bottommost position reached'''
                self.rewind_text = ""
                self.reset_animation ()
        return False

    def animate (self):
        '''The actual animation function'''
        self.source = None
        if self.state == -2:
            self.rewind_animate ()
        elif self.state == -1:
            self.rewind_text += "\n\n%s" % self.item
            self.state = 0
            self.animate ()
        elif self.state == 0:
            if self.pos:
                '''Move towards the top position'''
                self.pos = max (0, self.pos - 0.02)
                label_height = self.label.size_request ()[1]
                total_height = self.height + label_height
                real_pos = float (self.pos * self.height + label_height) \
                            / total_height
                self.current.set (0.5, real_pos, 0, 0)
                self.source = gobject.timeout_add (5, self.animate)
            else:
                '''Topmost position reached'''
                self.state = 1
                self.pos = 1.0
                self.source = gobject.timeout_add (self.timeout, self.animate)
        elif self.state == 1:
            '''Dont let selected labels vanish until they are unselected'''
            if self.label.get_selection_bounds () == ():
                self.state = 2
            self.source = gobject.timeout_add (5, self.animate)
        elif self.state == 2:
            if not self.next:
                self.state = -2
                self.last_label_height = self.label.size_request ()[1]
                self.reset_animation ()
                self.source = gobject.timeout_add (1, self.animate)
            elif self.pos:
                '''Move out of the visible region of the Layout'''
                self.pos = max (0, self.pos - 0.02)
                label_height = self.label.size_request ()[1]
                total_height = self.height + label_height
                real_pos = float (self.pos * label_height) \
                            / total_height
                self.current.set (0.5, real_pos, 0, 0)
                self.source = gobject.timeout_add (5, self.animate)
            else:
                '''Label has disappeared, bye bye'''
                self.reset_animation ()
        return False

    def make_label (self):
        '''Build a new label widget'''
        super (VertAnimatedLabel, self).make_label ()
        if not self.label:
            return
        self.label.set_size_request (self.width, -1)
        self.current = gtk.Alignment (0.0, 1.0)
        label_height = self.label.size_request ()[1]
        height = self.size_request ()[1]
        self.current.set_size_request (-1, 2 * label_height + height)
        self.current.add (self.label)
        self.put (self.current, 0, - label_height)
        self.pos = 1.0
        self.show_all ()

class HorzAnimatedLabel (AnimatedLabel):
    '''Horizontally animated label'''

    def animate (self):
        '''The actual animation function'''
        self.source = None
        if self.state == -2:
            self.reset_animation ()
        elif self.state <= 0:
            if self.pos != 0.5:
                '''Move towards the center position'''
                self.pos = max (0.5, self.pos - 0.02)
                self.current.set (self.pos, 0.5, 0, 0)
                self.source = gobject.timeout_add (5, self.animate)
            else:
                '''Center position reached, switch to return mode'''
                self.state = 1
                self.source = gobject.timeout_add (self.timeout, self.animate)
        elif self.state == 1:
            '''Dont let selected labels vanish until they are unselected'''
            if self.label.get_selection_bounds () == ():
                self.state = 2
            self.source = gobject.timeout_add (5, self.animate)
        elif self.state == 2:
            if self.pos:
                '''Disappear by moving left'''
                self.pos = max (0, self.pos - 0.02)
                self.current.set (self.pos, 0.5, 0, 0)
                self.source = gobject.timeout_add (5, self.animate)
            else:
                '''Left position reached, let's move on'''
                self.reset_animation ()
        return False

    def make_label (self):
        '''Build a new label widget'''
        super (HorzAnimatedLabel, self).make_label ()
        if not self.label:
            return
        self.label.set_size_request (-1, self.height)
        self.current = gtk.Alignment (1.0, 0.0)
        label_width = self.label.size_request ()[0]
        width = self.size_request ()[0]
        self.current.set_size_request (2 * label_width + width, -1)
        self.current.add (self.label)
        self.put (self.current, - label_width, 0)
        self.pos = 1.0
        self.show_all ()

class WindowedLabel (gtk.Label):
    '''Custom gtk.Label with an overlapping input-only gtk.gdk.Window'''

    event_window = None

    def __init__ (self, debug = False):
        '''Initialize object and plug all signals'''
        self.debug = debug
        super (WindowedLabel, self).__init__ ()

    def do_realize (self):
        '''Create a custom GDK window with which we will be able to play'''
        gtk.Label.do_realize (self)
        event_mask = self.get_events () | gtk.gdk.BUTTON_PRESS_MASK \
                                        | gtk.gdk.BUTTON_RELEASE_MASK \
                                        | gtk.gdk.KEY_PRESS_MASK
        self.event_window = gtk.gdk.Window (parent = self.get_parent_window (),
                                            window_type = gtk.gdk.WINDOW_CHILD,
                                            wclass = gtk.gdk.INPUT_ONLY,
                                            event_mask = event_mask,
                                            x = self.allocation.x,
                                            y = self.allocation.y,
                                            width = self.allocation.width,
                                            height = self.allocation.height)
        self.event_window.set_user_data (self)

    def do_unrealize (self):
        '''Destroy event window on unrealize'''
        self.event_window.set_user_data (None)
        self.event_window.destroy ()
        gtk.Label.do_unrealize (self)

    def do_size_allocate (self, allocation):
        '''Move & resize the event window to fit the Label's one'''
        gtk.Label.do_size_allocate (self, allocation)
        if self.flags () & gtk.REALIZED:
            self.event_window.move_resize (allocation.x, allocation.y,
                                           allocation.width, allocation.height)

    def do_map (self):
        '''Show event window'''
        gtk.Label.do_map (self)
        self.event_window.show ()
        '''Raise the event window to make sure it is over the Label's one'''
        self.event_window.raise_ ()

    def do_unmap (self):
        '''Hide event window on unmap'''
        self.event_window.hide ()
        gtk.Label.do_unmap (self)

gobject.type_register (WindowedLabel)

class HyperLink (WindowedLabel):
    '''Clickable www link label'''

    url       = ""
    menu      = None
    selection = None

    def __init__ (self, label, url):
        '''Initialize object'''
        super (HyperLink, self).__init__ ()
        markup = "<b><u>%s</u></b>" % label
        self.set_markup (markup)
        self.set_selectable (True)
        self.url = url
        self.create_menu ()
        link_color = self.style_get_property ("link-color") 
        if not link_color:
            link_color = default_link_color
        self.modify_fg (gtk.STATE_NORMAL, link_color)

    def open_url (self, *args):
        '''Use GNOME API to open the url'''
        try:
            gtk.show_uri (self.get_screen(), self.url, 0)
        except Exception, e:
            print '''Warning: could not open "%s": %s''' % (self.url, e)

    def copy_url (self, *args):
        '''Copy URL to Clipboard'''
        clipboard = gtk.clipboard_get ("CLIPBOARD")
        clipboard.set_text (self.url)

    def create_menu (self):
        '''Create the popup menu that will be displayed upon right click'''
        self.menu = gtk.Menu ()
        open_item = gtk.ImageMenuItem (_("_Open URL"))
        open_image = gtk.image_new_from_stock (gtk.STOCK_OPEN,
                                               gtk.ICON_SIZE_MENU)
        open_item.set_image (open_image)
        open_item.connect ("activate", self.open_url)
        open_item.show ()
        self.menu.append (open_item)
        copy_item = gtk.ImageMenuItem (_("_Copy URL"))
        copy_image = gtk.image_new_from_stock (gtk.STOCK_COPY,
                                               gtk.ICON_SIZE_MENU)
        copy_item.set_image (copy_image)
        copy_item.connect ("activate", self.copy_url)
        copy_item.show ()
        self.menu.append (copy_item)

    def display_menu (self, button, time, place = False):
        '''Display utility popup menu'''
        if place:
            alloc = self.get_allocation ()
            pos = self.event_window.get_origin ()
            x = pos[0]
            y = pos[1] + alloc.height
            func = lambda *a: (x, y, True)
        else:
            func = None
        self.menu.popup (None, None, func, button, time)

    def do_map (self):
        '''Select the HAND2 cursor on map'''
        WindowedLabel.do_map (self)
        cursor = gtk.gdk.Cursor (gtk.gdk.HAND2)
        self.event_window.set_cursor (cursor)

    def do_button_press_event (self, event):
        '''Update selection bounds infos or display popup menu'''
        if event.button == 1:
            self.selection = self.get_selection_bounds ()
        elif event.button == 3:
            self.display_menu (event.button, event.time)
            return True
        WindowedLabel.do_button_press_event (self, event)

    def do_button_release_event (self, event):
        '''Open url if selection hasn't changed since initial press'''
        if event.button == 1:
            selection = self.get_selection_bounds ()
            if selection == self.selection:
                self.open_url ()
                return True
        WindowedLabel.do_button_release_event (self, event)

    def do_key_press_event (self, event):
        '''Open url when Return key is pressed'''
        if event.keyval == gtk.keysyms.Return:
            self.open_url ()
            return True
        elif event.keyval == gtk.keysyms.Menu \
          or (event.keyval == gtk.keysyms.F10 \
              and event.state & gtk.accelerator_get_default_mod_mask() == \
                  gtk.gdk.SHIFT_MASK):
            self.display_menu (event.keyval, event.time, place = True)
            return True
        WindowedLabel.do_key_press_event (self, event)

gobject.type_register (HyperLink)

class GnomeLogo (gtk.Widget):
    '''Simple widget displaying a colored GNOME logo'''

    _surface = None
    _parent  = None
    _file    = None

    def __init__ (self, parent, file):
        '''Initialize object and do the initial painting'''
        gtk.Widget.__init__ (self)

        self._parent = parent
        self._file = file

        self.paint_surface ()

        width = self._surface.get_width ()
        height = self._surface.get_height ()
        self.set_size_request (width, height)

        self.set_flags(self.flags() | gtk.NO_WINDOW)

    def paint_surface (self):
        '''Paint image and color overlay'''
        self._surface = cairo.ImageSurface.create_from_png (self._file)

        text_color = self._parent.get_style ().fg[gtk.STATE_NORMAL]

        cr = cairo.Context (self._surface)
        cr.set_source_rgb (float (text_color.red) / 65535,
                           float (text_color.green) / 65535,
                           float (text_color.blue) / 65535)
        cr.set_operator (cairo.OPERATOR_ATOP)
        cr.paint ()

    def do_expose_event (self, event):
        '''Paint the saved surface to the widget context'''
        cr = self.window.cairo_create ()

        cr.set_operator (cairo.OPERATOR_OVER)
        cr.set_source_surface (self._surface, 10, 10)
        cr.paint ()

    def do_style_set (self, *args):
        '''Style changed, let's repaint image'''
        self.paint_surface ()
        self.queue_draw ()

gobject.type_register (GnomeLogo)

class GnomeAboutHeader (gtk.Layout):
    '''Pretty header for gnome-about'''

    links   = []

    width   = 0
    height  = 0

    def __init__ (self, links):
        '''Initialize object, plug map signal'''
        super (GnomeAboutHeader, self).__init__ () 
        self.links = links

    def do_realize (self):
        '''Load header and build links'''
        gtk.Layout.do_realize (self)

        current_x = 0
        current_y = 0
        base_y = 0

        header = self.load_header ()
        if header:
            self.put (header, 0, 0)
            current_y = header.get_pixbuf ().get_height ()
            base_y = current_y + 4
            line = self.create_line ()
            image = gtk.Image ()
            image.set_from_pixmap (line, None)
            self.put (image, 0, current_y)

        logo = self.load_logo ()
        if logo:
            self.put (logo, 0, 0)
            logo_size = logo.get_size_request ()
            current_x += logo_size[0] + 25
            current_y = logo_size[1] + 20

        dot = self.create_dot ()

        def make_link_widget (link):
            '''Helper function which makes an HyperLink and shows it'''
            label = HyperLink (link[0], link[1])
            label.show_all ()
            return label

        widgets = map (make_link_widget, self.links)
        put_widgets = 0
        for widget in widgets:
            if put_widgets > 0:
                if dot:
                    image = gtk.Image ()
                    image.set_from_pixmap (dot, None)
                    self.put (image, current_x + 5, base_y + 6)
                current_x += 16
            self.put (widget, current_x, base_y)
            current_x += widget.size_request ()[0]
            put_widgets += 1

        self.width = current_x + 10
        self.height = current_y
        self.set_size_request (self.width, self.height)
        self.show_all ()

    def load_header (self):
        '''Load a random header image as a gtk.Image'''

        directory = locate_file ("headers")
        if not directory:
            print "Warning: header images directory not found."
            return None

        try:
            headers = os.listdir (directory)
        except:
            print "Warning: failed to read header images directory."
            return None

        headers = filter (lambda s: s[-4:] in (".png", ".gif"), headers)
        header = random.choice (headers)

        file = os.path.join (directory, header)
        try:
            pixbuf = gtk.gdk.pixbuf_new_from_file (file)
        except:
            print '''Warning: failed to load header image "%s".''' % file
            return None

        image = gtk.Image ()
        image.set_from_pixbuf (pixbuf)

        return image

    def load_logo (self):
        '''Load a GNOME foot logo as a gtk.Image'''

        file = locate_file (LOGO_FILE)
        if not file:
            print '''Warning: GNOME logo file "%s" not found.''' % LOGO_FILE
            return None

        try:
            logo = GnomeLogo (self, file)
        except Exception:
            print '''Warning: failed to load GNOME logo image "%s".''' % file
            return None

        return logo

    def create_dot (self):
        '''Create a pixmap containing a simple dot'''
        pixmap = gtk.gdk.Pixmap (self.window, 6, 6)
        context = pixmap.cairo_create ()
        context.set_operator (cairo.OPERATOR_SOURCE)
        context.set_source_color (self.style.bg[self.state])
        context.paint ()
        context.set_operator (cairo.OPERATOR_OVER)
        context.set_source_color (self.style.fg[self.state])
        context.arc (3, 3, 2.3, 0, 2 * pi)
        context.fill ()
        return pixmap

    def create_line (self, width = 2000, height = 1):
        '''Create a pixmap containing a simple line'''
        pixmap = gtk.gdk.Pixmap (self.window, width, height)
        context = pixmap.cairo_create ()
        context.set_operator (cairo.OPERATOR_SOURCE)
        context.set_source_rgb (0, 0, 0)
        context.paint ()
        return pixmap

gobject.type_register (GnomeAboutHeader)

class GnomeAbout (gtk.Dialog):
    '''Super pretty About Dialog for the GNOME Desktop'''

    header               = None
    contributors         = None
    description_messages = GettableList ()
    system_infos         = []

    def __init__ (self, ui = True):
        '''Initialize underlying gnome.Program, Contributors list, UI...'''
        super (GnomeAbout, self).__init__ (_("About the GNOME Desktop"),
                                           buttons = (gtk.STOCK_CLOSE,
                                                      gtk.RESPONSE_CLOSE))

        # Immediately fetch system infos to load description messages
        self.system_infos = self.get_system_infos ()

        if not ui:
            return

        self.contributors = GnomeContributors ()

        icon_file = ICONDIR + "/gnome-logo-icon-transparent.png"
        try:
            self.set_icon_from_file (icon_file)
        except gobject.GError:
            pass

        self.create_ui ()

        self.set_default_response (gtk.RESPONSE_CLOSE)
        self.set_position (gtk.WIN_POS_CENTER)
        map (lambda prop: self.set_property (prop[0], prop[1]),
             [("allow-grow", False), ("allow-shrink", False)])
        self.connect ("delete-event", gtk.main_quit)
        self.connect ("response", self.response_callback)

    def gnome_version (self):
        '''Output basic GNOME version information to console'''
        def print_info (info):
            infos_dict = {"name": info[0], "value": info[1]}
            # Translators: %(name)s and %(value)s should not be translated:
            # it's a way to identify a string, so just handle them like %s
            print _("%(name)s: %(value)s") % infos_dict
        map (print_info, self.system_infos)

    def create_ui (self):
        '''Fill our Dialog with some lovely widgets'''
        self.set_has_separator (False)
        main_box = self.get_children ()[0] # Get the internal Dialog VBox

        '''Pretty header'''
        self.header = GnomeAboutHeader (header_links)
        main_box.pack_start (self.header)

        welcome_label = WindowedLabel ()
        welcome_label.set_markup ("<big><big><b>%s</b></big></big>" % \
                                  _("Welcome to the GNOME Desktop"))
        main_box.pack_start (welcome_label)

        descriptions_label = VertAnimatedLabel (self.description_messages,
                                                300, 120,
                                                DESCRIPTION_DELAY, "%s")
        welcome_label.connect ("button-press-event",
                               descriptions_label.on_button_press)
        box = gtk.EventBox ()
        alignment = gtk.Alignment (0.5, 0.5)
        alignment.set_padding (10, 10, 0, 0)
        alignment.add (descriptions_label)
        box.connect ("button-press-event", descriptions_label.on_button_press)
        box.add (alignment)
        main_box.pack_start (box)

        by_label = WindowedLabel (True)
        by_label.set_markup ("<big><b>%s</b></big>" % _("Brought to you by:"))
        main_box.pack_start (by_label)

        alignment = gtk.Alignment (0.5, 0.5)
        '''Realize the header right now to get everything (especially the
contributors list) correctly positionned and sized.'''
        self.header.realize ()
        width = self.header.width
        label = HorzAnimatedLabel (self.contributors, width,
                                   30, CONTRIBUTOR_DELAY, "<b>%s</b>")
        by_label.connect ("button-press-event", label.on_button_press)
        label.show_all ()
        alignment.add (label)

        main_box.pack_start (alignment)

        '''System info labels'''
        def make_info_label (info):
            if not info[1]:
                return False
            label = gtk.Label ()
            infos_dict = {"name": info[0], "value": info[1]}
            # Translators: %(name)s and %(value)s should not be translated:
            # it's a way to identify a string, so just handle them like %s
            label.set_markup (_("<b>%(name)s:</b> %(value)s") % infos_dict)
            label.set_selectable (True)
            label.connect ("focus-out-event",
                           lambda l, e: l.select_region (-1, -1))
            alignment = gtk.Alignment (0, 0.5)
            alignment.set_padding (0, 4, 8, 0)
            alignment.add (label)
            return alignment

        info_labels = map (make_info_label, self.system_infos)
        info_labels = filter (lambda l: l <> False, info_labels)
        map (lambda l: main_box.pack_start (l, False, False), info_labels)

        main_box.show_all ()

    def get_system_infos (self):
        '''Fetch various system infos'''
        file = locate_file ("gnome-version.xml")
        if not file:
            print '''Warning: "gnome-version.xml" file not found.'''
            return []

        f = open (file, "r")
        try:
            document = xml.dom.minidom.parse (f)
        finally:
            f.close ()

        if document.firstChild.nodeName != "gnome-version":
            print '''Warning: corrupted "gnome-version.xml".'''
            return []

        infos = {
                    "platform" : "",
                    "minor" : "",
                    "micro" : "",
                    "distributor" : "",
                    "date" : ""
                }

        for node in document.firstChild.childNodes:
            if node.nodeName in infos:
                infos[node.nodeName] = node.firstChild.nodeValue
            elif node.nodeName == "description":
                self.load_description_messages (node)

        '''Format version'''
        if not len (infos["platform"]):
            version = ""
        elif not len (infos["minor"]):
            version = infos["platform"]
        elif not len (infos["micro"]):
            version = "%s.%s" % (infos["platform"], infos["minor"])
        else:
            version = "%s.%s.%s" % (infos["platform"], infos["minor"],
                                    infos["micro"])

        date = cleanup_date (infos["date"])

        retval = []
        if version:
            retval.append((_("Version"), version))
        if infos["distributor"]:
            retval.append((_("Distributor"), infos["distributor"]))
        if date:
            retval.append((_("Build Date"), date))

        return retval

    def load_description_messages (self, node):
        '''Find the best translation of each description message'''
        languages = get_language_names () + [""]
        def desc_filter_func (node):
            '''Helper filter function for XML descriptions'''
            return node.nodeName == "p" \
               and node.getAttribute ("xml:lang") in languages
        def desc_sort_func (a, b):
            '''Helper sorting function to sort translated messages'''
            return cmp (languages.index (a), languages.index (b))
        descs = filter (desc_filter_func, node.childNodes)
        raw_descs = filter (lambda n: n.getAttribute ("xml:lang") == "", descs)
        i = -1
        translations = []
        for desc in raw_descs:
            new_i = descs.index (desc)
            if i != - 1:
                # Append the previous description
                translations.append (descs[i:new_i])
            i = new_i
        # Open ended to retrieve all translation for last description
        translations.append (descs[i:])
        messages = GettableList ()
        for block in translations:
            sorted_descs = sorted (block, cmp = desc_sort_func,
                                   key = lambda n: n.getAttribute ("xml:lang"))
            best = sorted_descs[0].firstChild.nodeValue
            messages.append (best)
        self.description_messages = messages

    def response_callback (self, widget, response):
        '''Handle dialog response when Close button is triggered'''
        if response == gtk.RESPONSE_CLOSE:
            gtk.main_quit ()

if __name__ == "__main__":
    parser = OptionParser (
            option_list = [
                    make_option ("--gnome-version",
                                 action="store_true",
                                 dest="gnome_version",
                                 help=_("Display information on this GNOME version")),
		    # FIXME: remove this when we can get all the default
		    # options
                    make_option ("--version",
                                 action="store_true",
                                 dest="version",
                                 help=VERSION),
                ])
    #FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=496278
    parser.parse_args (sys.argv)
    if parser.values.gnome_version:
        about = GnomeAbout (ui = False)
        about.gnome_version ()
    elif parser.values.version:
        print "GNOME gnome-about %s" % (VERSION)
    else:
        about = GnomeAbout ()
        about.show_all ()
        try:
            gtk.main ()
        except KeyboardInterrupt:
            pass
