#!/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, os

import glib, gtk, gobject, pynotify

import jockey.ui
from jockey.oslib import OSLib

class GtkUI(jockey.ui.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 UI
        self.widgets = gtk.Builder()
        ui_path = '/usr/share/jockey/jockey-gtk.ui'
        if not os.path.exists(ui_path):
            ui_path = os.path.join(os.path.dirname(__file__), 'jockey-gtk.ui')
        self.widgets.add_from_file(ui_path)
        self.widgets.connect_signals(self)

        self.w('label_license_label').set_label(self.string_license_label)
        self.w('linkbutton_licensetext').set_label('(%s)' % self.string_details)
        self.w('dialog_licensetext').set_title(self.string_license_dialog_title)

    def ui_show_main(self):
        '''Show main window.'''

        # check $DISPLAY validity, etc.
        try:
            gtk.init_check()
        except RuntimeError:
            self.error_msg('Could not initialize GTK: invalid $DISPLAY?')
            sys.exit(1)

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

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

        # initialize handler treeview (but only do it once)
        if not self.treeview.get_columns():
            self.treeview.set_headers_visible(False)

            # icon
            pixbuf_renderer = gtk.CellRendererPixbuf()
            col_icon = gtk.TreeViewColumn()
            col_icon.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
            col_icon.set_expand(False)
            col_icon.pack_start(pixbuf_renderer, False)
            col_icon.set_attributes(pixbuf_renderer, pixbuf=1)
            self.treeview.append_column(col_icon)

            # name
            text_renderer = gtk.CellRendererText()
            col_name = gtk.TreeViewColumn()
            col_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
            col_name.set_expand(True)
            col_name.pack_start(text_renderer, True)
            col_name.set_attributes(text_renderer, text=2)
            self.treeview.append_column(col_name)

        self.treeview.grab_focus()
        self.update_driver_info_ui(None)
        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)

        # default height of details scrollwindow is too small
        self.w('dialog_manager').set_default_size(-1, 550)

        # default vpane size
        if len(self.get_displayed_handlers()) < 2:
            self.w('vpaned1').set_position(36)
        elif len(self.get_displayed_handlers()) < 4:
            self.w('vpaned1').set_position(72)
        else:
            self.w('vpaned1').set_position(100)

        # 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', lambda widget: self.ui_show_main())
        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 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_progress_start(self, title, description, total):
        '''Create a progress dialog.'''

        self.w('progress').set_fraction(0)
        self.w('label_description').set_label(description)
        self.w('window_progress').set_title(title)
        self.w('window_progress').set_transient_for(self.w('dialog_manager'))
        self.w('window_progress').show()
        self.cancel_progress = False

    def ui_progress_update(self, current, total):
        '''Update status of current progress dialog.
        
        current/total specify the number of steps done and total steps to
        do, or -1 if it cannot be determined. In this case the dialog should
        display an indeterminated progress bar (bouncing back and forth).

        This should return True to cancel, and False otherwise.
        '''
        if current < 0 or total < 0:
            self.w('progress').set_pulse_step(0.1)
            self.w('progress').pulse()
        else:
            self.w('progress').set_fraction(float(current)/total)
        return self.cancel_progress

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

        self.w('window_progress').hide()

    #
    # helper functions
    #

    def w(self, widget):
        '''Shortcut for getting an UI widget from GtkBuilder.'''

        return self.widgets.get_object(widget)

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

        # handler ID, icon, name
        self.model = gtk.ListStore(gobject.TYPE_STRING, gtk.gdk.Pixbuf, gobject.TYPE_STRING)

        theme = gtk.icon_theme_get_default()

        cur_path = self.treeview.get_cursor()[0]

        for h_id in self.get_displayed_handlers():
            info = self.get_ui_driver_info(h_id)
            if info['needs_reboot']:
                pixbuf = theme.load_icon(gtk.STOCK_REFRESH, 16, gtk.ICON_LOOKUP_USE_BUILTIN)
            elif info['enabled']:
                try:
                    pixbuf = theme.load_icon('jockey-enabled', 16, 0)
                except glib.GError:
                    pixbuf = theme.load_icon(gtk.STOCK_YES, 16, gtk.ICON_LOOKUP_USE_BUILTIN)
            else:
                try:
                    pixbuf = theme.load_icon('jockey-disabled', 16, 0)
                except glib.GError:
                    pixbuf = theme.load_icon(gtk.STOCK_NO, 16, gtk.ICON_LOOKUP_USE_BUILTIN)
            iter = self.model.append([h_id, pixbuf, info['name']])
            if not cur_path:
                cur_path = self.model.get_path(iter)

        self.treeview.set_model(self.model)
        if cur_path:
            self.treeview.set_cursor(cur_path)
        self.treeview.expand_all()

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

    def update_driver_info_ui(self, handler_id):
        '''Update UI elements which show the driver details.

        If handler_id is None, then no driver is selected, no information
        shown, and the appropriate controls are disabled.
        '''
        info = self.get_ui_driver_info(handler_id)

        self.w('label_drivername').set_label('<b>%s</b>' % info['name'])
        self.current_driver_name = info['name']
        self.w('textview_description').get_buffer().set_text(info['description'])
        if info['certified'] == None:
            self.w('image_certification').hide()
        elif info['certified']:
            self.w('image_certification').show()
            self.w('image_certification').set_from_icon_name('jockey-certified', gtk.ICON_SIZE_BUTTON)
        else:
            self.w('image_certification').show()
            self.w('image_certification').set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_BUTTON)
        self.w('label_certification').set_label(info['certification_label'])
        if info['free'] == None:
            self.w('image_license').hide()
            self.w('label_license_label').hide()
        elif info['free']:
            self.w('image_license').show()
            self.w('label_license_label').show()
            self.w('image_license').set_from_icon_name('jockey-free', gtk.ICON_SIZE_BUTTON)
        else:
            self.w('image_license').show()
            self.w('label_license_label').show()
            self.w('image_license').set_from_icon_name('jockey-proprietary',
                gtk.ICON_SIZE_BUTTON)
        self.w('label_license').set_label(info['license_label'])
        if info['license_text']:
            self.w('linkbutton_licensetext').show()
            self.current_license_text = info['license_text']
        else:
            self.w('linkbutton_licensetext').hide()
            self.current_license_text = None
        if info['enabled'] == None:
            self.w('image_enabled').hide()
        elif info['needs_reboot']:
            self.w('image_enabled').show()
            self.w('image_enabled').set_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_BUTTON)
        elif info['enabled']:
            self.w('image_enabled').show()
            self.w('image_enabled').set_from_icon_name('jockey-enabled', gtk.ICON_SIZE_BUTTON)
        else:
            self.w('image_enabled').show()
            self.w('image_enabled').set_from_icon_name('jockey-disabled', gtk.ICON_SIZE_BUTTON)
        self.w('label_status').set_label(info['status_label'])

        if not info['button_toggle_label']:
            self.w('button_toggle').set_label(self.string_button_enable)
            self.w('button_toggle').set_sensitive(False)
        else:
            self.w('button_toggle').set_sensitive(True)
            self.w('button_toggle').set_label(info['button_toggle_label'])
            if info['enabled']:
                self.w('button_toggle').set_image(
                    gtk.image_new_from_icon_name('jockey-disabled', gtk.ICON_SIZE_BUTTON))
            else:
                self.w('button_toggle').set_image(
                    gtk.image_new_from_icon_name('jockey-enabled', gtk.ICON_SIZE_BUTTON))

    #
    # 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_button_toggle_clicked(self, widget):
        self.treeview.set_sensitive(False)
        if self.set_handler_enable(self.model[self.treeview.get_cursor()[0]][0],
            'toggle', False):
            self.update_tree_model()
        self.treeview.set_sensitive(True)
        return True

    def on_treeview_drivers_cursor_changed(self, widget):
        path = widget.get_cursor()[0]
        if path:
            h_id = self.model[path][0]
        else:
            h_id = None
        self.update_driver_info_ui(h_id)
        return True

    def on_progress_cancel(self, *args):
        self.cancel_progress = True
        return True

    def on_linkbutton_licensetext_clicked(self, widget):
        self.w('label_license_drivername').set_label('<b>%s</b>' %
            self.current_driver_name)
        self.w('textview_license_text').get_buffer().set_text(self.current_license_text)
        self.w('dialog_licensetext').set_default_size(600, 480)
        self.w('dialog_licensetext').run()
        self.w('dialog_licensetext').hide()
        return True

if __name__ == '__main__':
    if not os.environ.get('DISPLAY'):
        print >> sys.stderr, 'This program needs a running X session. Please use jockey-text for a command line version of Jockey.'
        sys.exit(1)

    OSLib.inst = OSLib(client_only=True)
    u = GtkUI()
    sys.exit(u.run())
