#!/usr/bin/env python

# (c) 2007 Canonical Ltd.
#
# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.

'''GTK user interface implementation.'''

import sys, os.path

import gtk, gtk.glade, gobject, pynotify

from jockey.ui import AbstractUI
from jockey.oslib import OSLib

class GtkUI(AbstractUI):
    '''GTK user interface implementation.'''

    #
    # Implementation of required AbstractUI methods
    # 

    def convert_keybindings(self, str):
        '''Keyboard accelerator aware gettext() wrapper.
        
        This optionally converts keyboard accelerators to the appropriate
        format for the frontend.

        A double underscore ('__') is converted to a real '_'.
        '''
        # nothing to do for GTK
        return str

    def ui_init(self):
        '''Initialize UI.'''

        # load glade
        if os.path.exists('/usr/share/jockey/main.glade'):
            self.glade = gtk.glade.XML('/usr/share/jockey/main.glade')
        else:
            self.glade = gtk.glade.XML(os.path.join(os.path.dirname(__file__), 'main.glade'))

        self.glade.signal_autoconnect(self)

        self.treeview = self.w('treeview_drivers')

        self.w('dialog_manager').set_title(self.main_window_title())

        # initialize handler treeview
        text_renderer = gtk.CellRendererText()
        col1 = gtk.TreeViewColumn(self.string_handler)
        col1.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col1.set_expand(True)
        col1.pack_start(text_renderer, True)
        col1.set_attributes(text_renderer, text=3)
        self.treeview.append_column(col1)

        toggle_renderer = gtk.CellRendererToggle()
        toggle_renderer.set_property('activatable', True)
        toggle_renderer.connect('toggled', self.on_handler_changed)
        col2 = gtk.TreeViewColumn(self.string_enabled)
        col2.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col2.set_expand(False)
        col2.pack_start(toggle_renderer, True)
        col2.set_attributes(toggle_renderer, active=4, visible=7)
        self.treeview.append_column(col2)

        pixbuf_renderer = gtk.CellRendererPixbuf()
        text_renderer = gtk.CellRendererText()
        col3 = gtk.TreeViewColumn(self.string_status)
        col3.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col3.set_expand(False)
        col3.pack_start(pixbuf_renderer, False)
        col3.pack_end(text_renderer, True)
        col3.set_attributes(pixbuf_renderer, pixbuf=5)
        col3.set_attributes(text_renderer, text=6)
        self.treeview.append_column(col3)

        self.treeview.grab_focus()

        self.update_tree_model()

        # show help button?
        if not OSLib.inst.ui_help_available(self):
            self.w('button_help').hide()
            # EDGE is the default, but that centers if there is just one button
            self.w('button_box_main').set_layout(gtk.BUTTONBOX_END)

        # lift the curtain
        self.w('dialog_manager').show()

    def ui_main_loop(self):
        '''Main loop for the user interface.
        
        This should return if the user wants to quit the program, and return
        the exit code.
        '''
        gtk.main()
        self.w('dialog_manager').hide()

    def error_message(self, title, text):
        '''Present an error message box.'''

        self.msgbox = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
            buttons=gtk.BUTTONS_CLOSE, message_format=text)
        if title:
            self.msgbox.set_title(title)
        self.msgbox.run()
        self.msgbox.destroy()
        self.msgbox = None

    def confirm_action(self, title, text, subtext=None, action=None):
        '''Present a confirmation dialog.

        If action is given, it is used as button label instead of the default
        'OK'.  Return True if the user confirms, False otherwise.
        '''
        if action:
            self.msgbox = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION,
                message_format=text)
            self.msgbox.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
            self.msgbox.add_button(action, gtk.RESPONSE_OK).set_image(
                gtk.image_new_from_stock(gtk.STOCK_APPLY,
                gtk.ICON_SIZE_BUTTON))
        else:
            self.msgbox = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION,
                message_format=text, buttons=gtk.BUTTONS_OK_CANCEL)
        if subtext:
            self.msgbox.format_secondary_text(subtext)
        if title:
            self.msgbox.set_title(title)
        ret = self.msgbox.run()
        self.msgbox.destroy()
        self.msgbox = None
        return ret == gtk.RESPONSE_OK

    def ui_notification(self, title, text):
        '''Present a notification popup.

        This should preferably create a tray icon. Clicking on the tray icon or
        notification should run the GUI.
        '''
        pynotify.init('jockey')

        trayicon = gtk.status_icon_new_from_icon_name('jockey')
        trayicon.connect('activate', self.open_app)
        trayicon.set_visible(False)
        trayicon.set_tooltip(title)
        trayicon.set_visible(True)

        self.ui_idle()

        # creating the notification immediately causes the bubble to point into
        # the void; icon needs to settle first
        gobject.timeout_add (500, self.show_notification, 
            (title, text, trayicon))

    def show_notification(self, data):
        (title, text, trayicon) = data
        notify = pynotify.Notification(title, text, 'jockey')
        notify.set_urgency(pynotify.URGENCY_NORMAL)
        notify.attach_to_status_icon(trayicon)
        notify.set_timeout(10000)
        notify.show()

    def open_app(self, widget):
        OSLib.inst.open_app(self)

    def ui_idle(self):
        '''Process pending UI events and return.

        This is called while waiting for external processes such as package
        installers.
        '''
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_download_start(self, url, total_size):
        '''Create a progress dialog for a download of given URL.

        total_size specifes the number of bytes to download, or -1 if it cannot
        be determined. In this case the dialog should display an indeterminated
        progress bar (bouncing back and forth).
        '''
        self.w('progress_download').set_fraction(0)
        self.w('label_download_url').set_label(url)
        self.w('window_download_progress').set_title(self.string_download_progress_title)
        self.w('window_download_progress').show()
        self.cancel_download = False

    def ui_download_progress(self, current_size, total_size):
        '''Update download progress of current download.
        
        This should return True to cancel the current download, and False
        otherwise.
        '''
        self.w('progress_download').set_fraction(float(current_size)/total_size)
        return self.cancel_download

    def ui_download_finish(self):
        '''Close the current download progress dialog.'''

        self.w('window_download_progress').hide()

    #
    # helper functions
    #

    def w(self, widget):
        '''Shortcut for getting a glade widget.'''

        return self.glade.get_widget(widget)

    def update_tree_model(self):
        '''Update treeview to current set of handlers and their states.'''

        self.model = gtk.TreeStore(gobject.TYPE_PYOBJECT,
                                gobject.TYPE_PYOBJECT,
                                gobject.TYPE_PYOBJECT,
                                str, bool, gtk.gdk.Pixbuf, str,
                                bool # toggle visible or not
                                )

        theme = gtk.icon_theme_get_default()

        parents = {}
        for handler in sorted(self.handlers, key=lambda x: (x.ui_category(), x.name())):
            is_enabled = handler.enabled()
            is_used = handler.used()
            category = handler.ui_category()

            if category not in parents:
                parents[category] = self.model.append(None, [None, None, None,
                    category, None, None, None, False])

            if (is_enabled != is_used) and handler.changed():
                status, icon = self.string_restart, gtk.STOCK_REFRESH
            elif is_used:
                status, icon = self.string_in_use, gtk.STOCK_YES
            else:
                status, icon = self.string_not_in_use, gtk.STOCK_NO
            pixbuf = theme.load_icon(icon, 16, gtk.ICON_LOOKUP_USE_BUILTIN)
            self.model.append(parents[category], [ handler, is_enabled, is_used,
                                handler.name(), is_enabled, pixbuf,
                                status, True ])

        self.treeview.set_model(self.model)
        self.treeview.expand_all()

        self.w('label_heading').set_label('<span weight="bold">%s</span>\n\n%s' %
            self.main_window_text())

    #
    # event callbacks
    #

    def on_quit(self, *args):
        gtk.main_quit()
        return True

    def on_button_help_clicked(self, widget):
        OSLib.inst.ui_help(self)
        return True

    def on_handler_changed(self, widget, path):
        self.treeview.set_sensitive(False)
        if self.toggle_handler(self.model[path][0]):
            self.update_tree_model()
        self.treeview.set_sensitive(True)
        return True

    def on_treeview_drivers_cursor_changed(self, widget):
        tip = self.get_handler_tooltip(self.model[widget.get_cursor()[0]][0])
        if tip:
            self.treeview.set_tooltip_text(tip)
            self.treeview.set_property('has-tooltip', True)
        else:
            self.treeview.set_property('has-tooltip', False)
        return True

    def on_download_cancel(self, *args):
        self.cancel_download = True
        return True

if __name__ == '__main__':
    OSLib.inst = OSLib()
    u = GtkUI()
    sys.exit(u.run())
