#!/usr/bin/python

'''GTK Apport user interface.'''

# Copyright (C) 2007 - 2009 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@ubuntu.com>
# 
# 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.  See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.

import os.path, sys, subprocess, os, re
from apport import unicode_gettext as _

try:
    import gobject, gtk
    import apport.ui
except ImportError, e:
    # this can happen while upgrading python packages
    print >> sys.stderr, 'Could not import module, is a package upgrade in progress? Error:', e
    sys.exit(1)

class GTKUserInterface(apport.ui.UserInterface):
    '''GTK UserInterface.'''

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

        return self.widgets.get_object(widget)

    def __init__(self):
        apport.ui.UserInterface.__init__(self)

        # load UI
        gtk.window_set_default_icon_name('apport')
        self.widgets = gtk.Builder()
        self.widgets.set_translation_domain(self.gettext_domain)
        self.widgets.add_from_file(os.path.join(os.path.dirname(sys.argv[0]),
            'apport-gtk.ui'))
        assert self.widgets.connect_signals(self, None) is None

        # initialize tree model and view
        self.tree_model = gtk.TreeStore(gobject.TYPE_STRING)
        self.w('treeview_reportdetails').set_model(self.tree_model)

        column = gtk.TreeViewColumn("Report", gtk.CellRendererText(), text=0)
        self.w('treeview_reportdetails').append_column(column)

        # save the original strings of the dialogs (those which contain %s,
        # which we replace later)
        self.str_heading = self.w('label_heading').get_label()
        self.str_heading_reopen = self.w('label_heading_reopen').get_label()
        self.str_heading_package_error = self.w('label_heading_package_error').get_label()
        self.str_heading_kernel_error = self.w('label_heading_kernel_error').get_label()
        self.str_radio_complete = self.w('radiobutton_complete').get_label()
        self.str_radio_reduced = self.w('radiobutton_reduced').get_label()

    #
    # ui_* implementation of abstract UserInterface classes
    #

    def ui_present_crash(self, desktop_entry): 
        # adapt dialog heading and label appropriately
        if desktop_entry:
            heading = _('Sorry, %s closed unexpectedly') % desktop_entry.getName()
        elif self.report.has_key('ExecutablePath'):
            heading = _('Sorry, the program "%s" closed unexpectedly') % os.path.basename(self.report['ExecutablePath'])
        else:
            heading = _('Sorry, %s closed unexpectedly') % self.cur_package
        if desktop_entry and self.report.has_key('ExecutablePath') and \
	    os.path.dirname(self.report['ExecutablePath']) in os.environ['PATH'].split(':') and \
	    subprocess.call(['pgrep', '-x',
		os.path.basename(self.report['ExecutablePath']), 
		'-u', str(os.geteuid())], stdout=subprocess.PIPE) != 0:
            self.w('label_heading_reopen').set_markup(self.str_heading % heading)
            d = self.w('dialog_crash_reopen')
	    bl_checkbox = self.w('checkbutton_blacklist_reopen')
        else:
            self.w('label_heading').set_markup(self.str_heading % heading)
            d = self.w('dialog_crash')
	    bl_checkbox = self.w('checkbutton_blacklist')

        # don't steal focus when being called without arguments (i. e.
        # automatically launched)
        if len(sys.argv) == 1:
            d.set_focus_on_map(False)

        # show crash notification dialog
        response = d.run()
        d.hide()
        while gtk.events_pending():
            gtk.main_iteration(False)
	blacklist = bl_checkbox.get_active()
        if response == gtk.RESPONSE_YES:
            return {'action': 'report', 'blacklist': blacklist}
        elif response == gtk.RESPONSE_OK:
            return {'action': 'restart', 'blacklist': blacklist}
        else:
            return {'action': 'cancel', 'blacklist': blacklist}

    def ui_present_package_error(self):
	self.w('label_heading_package_error').set_markup(
	    self.str_heading_package_error % self.report['Package'])
        response = self.w('dialog_package_error').run()
        self.w('dialog_package_error').hide()
        while gtk.events_pending():
            gtk.main_iteration(False)
        if response == gtk.RESPONSE_YES:
            return 'report'
        else:
            return 'cancel'

    def ui_present_kernel_error(self):
        message = _('Your system encountered a serious kernel problem.')
        annotation = ''
        if self.report.has_key('Annotation'):
            annotation += self.report['Annotation'] + '\n\n'
        annotation += _('You can help the developers to fix the problem by reporting it.')

        label = self.str_heading_kernel_error % (message, annotation)
        self.w('label_heading_kernel_error').set_markup(label)

        response = self.w('dialog_kernel_error').run()
        self.w('dialog_kernel_error').hide()
        while gtk.events_pending():
            gtk.main_iteration(False)
        if response == gtk.RESPONSE_YES:
            return 'report'
        else:
            return 'cancel'

    def ui_present_report_details(self, is_update):
        # report contents in expander
        self.tree_model.clear()
        row = 0
        for key in sorted(self.report):
            keyiter = self.tree_model.insert_before(None, None)
            self.tree_model.set_value(keyiter, 0, key)

            valiter = self.tree_model.insert_before(keyiter, None)
            if not hasattr(self.report[key], 'gzipvalue') and \
                hasattr(self.report[key], 'isspace') and \
                not self.report._is_binary(self.report[key]):
                self.tree_model.set_value(valiter, 0, self.report[key])
                # expand the row if the value has less than 5 lines
                if len(filter(lambda c: c == '\n', self.report[key])) < 4:
                    self.w('treeview_reportdetails').expand_row(row, False)
            else:
                self.tree_model.set_value(valiter, 0, _('(binary data)'))

            row += 1

        # complete/reduced radio buttons
        if self.report.has_key('CoreDump'):
            self.w('radiobutton_complete').set_label(self.str_radio_complete % 
                self.format_filesize(self.get_complete_size()))
            self.w('radiobutton_reduced').set_label(self.str_radio_reduced % 
                self.format_filesize(self.get_reduced_size()))
            if self.report.has_useful_stacktrace():
                self.w('radiobutton_complete').set_sensitive(True)
                self.w('radiobutton_complete').show()
                self.w('radiobutton_reduced').show()
            else:
                self.w('radiobutton_complete').set_sensitive(False)
                self.w('radiobutton_reduced').hide()
        else:
            self.w('radiobutton_complete').hide()
            self.w('radiobutton_reduced').hide()

        if is_update:
            self.w('label_bug_instructions').set_label(
                    _('Send this data to the developers?'))
            self.w('expander_details').set_expanded(True)
            self.w('dialog_bugreport').set_default_size(400, -1)

        response = self.w('dialog_bugreport').run()
        self.w('dialog_bugreport').hide()
        while gtk.events_pending():
            gtk.main_iteration(False)

        if response == gtk.RESPONSE_OK:
            if self.w('radiobutton_complete').get_active():
                return 'full'
            else:
                return 'reduced'
        else:
            return 'cancel'

    def ui_info_message(self, title, text):
        if 'http://' in text or 'https://' in text:
            md = gtk.MessageDialog(type=gtk.MESSAGE_INFO,
                buttons=gtk.BUTTONS_CLOSE)
            # turn URLs into links
            md.set_markup(re.sub(r'(https?://[a-zA-Z0-9._-]+(?:[a-zA-Z0-9_#?%/-])*)',
                r'<a href="\1">\1</a>', text))
        else:
            md = gtk.MessageDialog(type=gtk.MESSAGE_INFO,
                buttons=gtk.BUTTONS_CLOSE, message_format=text)
        md.set_title(title)
        md.run()
        md.hide()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_error_message(self, title, text):
        if 'http://' in text or 'https://' in text:
            md = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                buttons=gtk.BUTTONS_CLOSE)
            # turn URLs into links
            md.set_markup(re.sub(r'(https?://[a-zA-Z0-9._-]+(?:[a-zA-Z0-9_#?%/-])*)',
                r'<a href="\1">\1</a>', text))
        else:
            md = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                buttons=gtk.BUTTONS_CLOSE, message_format=text)
        md.set_title(title)
        md.run()
        md.hide()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_start_info_collection_progress(self):
        self.w('progressbar_information_collection').set_fraction(0)
        self.w('window_information_collection').show()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_pulse_info_collection_progress(self):
        self.w('progressbar_information_collection').pulse()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_stop_info_collection_progress(self):
        self.w('window_information_collection').hide()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_start_upload_progress(self):
        '''Open a window with an definite progress bar, telling the user to
        wait while debug information is being uploaded.'''

        self.w('progressbar_upload').set_fraction(0)
        self.w('window_report_upload').show()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_set_upload_progress(self, progress):
        '''Set the progress bar in the debug data upload progress
        window to the given ratio (between 0 and 1, or None for indefinite
        progress).
        
        This function is called every 100 ms.'''

        if progress:
            self.w('progressbar_upload').set_fraction(progress)
        else:
            self.w('progressbar_upload').set_pulse_step(0.1)
            self.w('progressbar_upload').pulse()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_stop_upload_progress(self):
        '''Close debug data upload progress window.'''

        self.w('window_report_upload').hide()
        while gtk.events_pending():
            gtk.main_iteration(False)

    def ui_question_yesno(self, text):
        '''Show a yes/no question.

        Return True if the user selected "Yes", False if selected "No" or
        "None" on cancel/dialog closing.
        '''
        md = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION,
            buttons=gtk.BUTTONS_YES_NO, message_format=text)
        result = md.run()
        md.hide()
        while gtk.events_pending():
            gtk.main_iteration(False)
        if result == gtk.RESPONSE_YES:
            return True
        if result == gtk.RESPONSE_NO:
            return False
        return None

    def ui_question_choice(self, text, options, multiple):
        '''Show an question with predefined choices.

        options is a list of strings to present. If multiple is True, they
        should be check boxes, if multiple is False they should be radio
        buttons.

        Return list of selected option indexes, or None if the user cancelled.
        If multiple == False, the list will always have one element.
        '''
        d = self.w('dialog_choice')
        d.set_default_size(400, -1)
        self.w('label_choice_text').set_label(text)

        # remove previous choices
        self.w('vbox_choices').foreach(lambda w, _: w.destroy(), None)

        b = None
        for option in options:
            if multiple:
                b = gtk.CheckButton(option)
            else:
                # use previous radio button as group
                b = gtk.RadioButton(b, option)
            self.w('vbox_choices').pack_start(b)
        self.w('vbox_choices').show_all()

        result = d.run()
        d.hide()
        if result != gtk.RESPONSE_OK:
            return None

        index = 0
        result = []
        for c in self.w('vbox_choices').get_children():
            if c.get_active():
                result.append(index)
            index += 1
        return result

    def ui_question_file(self, text):
        '''Show a file selector dialog.

        Return path if the user selected a file, or None if cancelled.
        '''
        md = gtk.FileChooserDialog(text,
                parent=self.w('window_information_collection'),
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        result = md.run()
        md.hide()
        while gtk.events_pending():
            gtk.main_iteration(False)
        if result == gtk.RESPONSE_OK:
            return md.get_filenames()[0]
        else:
            return None

    #
    # Event handlers
    #

    def on_progress_window_close_event(self, widget, event=None):
        self.w('window_information_collection').hide()
        self.w('window_report_upload').hide()
        sys.exit(0)
        return True

    def on_expander_details_activate(self, widget):
        # signal is sent before actually expanding/collapsing, thus this
        # requires negation
        self.w('dialog_bugreport').set_resizable(not self.w('expander_details').get_expanded())
        while gtk.events_pending():
            gtk.main_iteration(False)
        return True

if __name__ == '__main__':
    if not os.environ.get('DISPLAY'):
        print >> sys.stderr, 'This program needs a running X session. Please see "man apport-cli" for a command line version of Apport.'
        sys.exit(1)
    app = GTKUserInterface()
    app.run_argv()
