[Reportbug-commits] r643 - in trunk (5 files)

morph-guest at users.alioth.debian.org morph-guest at users.alioth.debian.org
Mon Sep 1 22:02:33 UTC 2008


    Date: Monday, September 1, 2008 @ 22:02:31
  Author: morph-guest
Revision: 643

added GTK+ UI, thanks to Luca Bruno

Added:
  trunk/reportbug/ui/gtk2_ui.py
Modified:
  trunk/debian/changelog
  trunk/debian/control
  trunk/debian/copyright
  trunk/reportbug/bugreport.py

Modified: trunk/debian/changelog
===================================================================
--- trunk/debian/changelog	2008-09-01 21:36:23 UTC (rev 642)
+++ trunk/debian/changelog	2008-09-01 22:02:31 UTC (rev 643)
@@ -31,8 +31,19 @@
     - bin/{querybts,reportbug}
       - changed for new UIs list location
 
- -- Sandro Tosi <matrixhasu at gmail.com>  Mon, 01 Sep 2008 22:40:41 +0200
+  * Added GTK+ UI
+    [ Luca Bruno ]
+    - reportbug/ui/gtk2_ui.py
+      + GTK+ interface
 
+    [ Sandro Tosi ]
+    - debian/control
+      + added python-gtk2 to suggests
+    - debian/copyright
+      - added gtk2 ui copyright
+
+ -- Sandro Tosi <matrixhasu at gmail.com>  Mon, 01 Sep 2008 23:47:34 +0200
+
 reportbug (3.45) unstable; urgency=low
 
   * reportbuglib/reportbug_ui_urwid.py

Modified: trunk/debian/control
===================================================================
--- trunk/debian/control	2008-09-01 21:36:23 UTC (rev 642)
+++ trunk/debian/control	2008-09-01 22:02:31 UTC (rev 643)
@@ -15,7 +15,7 @@
 Package: reportbug
 Architecture: all
 Depends: ${python:Depends}, apt, python-reportbug
-Suggests: postfix | exim4 | mail-transport-agent, gnupg | pgp, debconf-utils (>> 1.1.0), debsums, file (>> 1.30), dlocate, python-urwid
+Suggests: postfix | exim4 | mail-transport-agent, gnupg | pgp, debconf-utils (>> 1.1.0), debsums, file (>> 1.30), dlocate, python-urwid, python-gtk2
 Conflicts: python-urwid (<< 0.9.8-1), python-central (<< 0.5.13)
 XB-Python-Version: ${python:Versions}
 Description: reports bugs in the Debian distribution

Modified: trunk/debian/copyright
===================================================================
--- trunk/debian/copyright	2008-09-01 21:36:23 UTC (rev 642)
+++ trunk/debian/copyright	2008-09-01 22:02:31 UTC (rev 643)
@@ -63,3 +63,24 @@
 License: PD
     checks/compare_pseudo-pkgs_lists.py was placed in public domain by Sandro
     Tosi <matrixhasu at gmail.com>
+
+Files: reportbug/ui/gtk2_ui.py
+Copyright:
+    Copyright (C) 2006 Philipp Kern <pkern at debian.org>
+    Copyright (C) 2008 Luca Bruno <lethalman88 at gmail.com>
+License: other
+    # This program is freely distributable per the following license:
+    #
+    ##  Permission to use, copy, modify, and distribute this software and its
+    ##  documentation for any purpose and without fee is hereby granted,
+    ##  provided that the above copyright notice appears in all copies and that
+    ##  both that copyright notice and this permission notice appear in
+    ##  supporting documentation.
+    ##
+    ##  I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+    ##  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+    ##  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+    ##  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+    ##  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+    ##  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+    ##  SOFTWARE.

Modified: trunk/reportbug/bugreport.py
===================================================================
--- trunk/reportbug/bugreport.py	2008-09-01 21:36:23 UTC (rev 642)
+++ trunk/reportbug/bugreport.py	2008-09-01 22:02:31 UTC (rev 643)
@@ -98,7 +98,7 @@
 
         body = getattr(self, 'body', u'')
         if self.mode < utils.MODE_ADVANCED:
-            body = NEWBIELINE+u'\n\n'+body
+            body = utils.NEWBIELINE+u'\n\n'+body
         elif not body:
             body = u'\n'
 

Added: trunk/reportbug/ui/gtk2_ui.py
===================================================================
--- trunk/reportbug/ui/gtk2_ui.py	                        (rev 0)
+++ trunk/reportbug/ui/gtk2_ui.py	2008-09-01 22:02:31 UTC (rev 643)
@@ -0,0 +1,1065 @@
+# a graphical (GTK+) user interface
+#   Written by Luca Bruno <lethalman88 at gmail.com>
+#   Based on gnome-reportbug work done by Philipp Kern <pkern at debian.org>
+#   Copyright (C) 2006 Philipp Kern
+#   Copyright (C) 2008 Luca Bruno
+#
+# This program is freely distributable per the following license:
+#
+##  Permission to use, copy, modify, and distribute this software and its
+##  documentation for any purpose and without fee is hereby granted,
+##  provided that the above copyright notice appears in all copies and that
+##  both that copyright notice and this permission notice appear in
+##  supporting documentation.
+##
+##  I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+##  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
+##  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+##  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+##  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+##  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+##  SOFTWARE.
+#
+# Version ##VERSION##; see changelog for revision history
+
+import gtk
+from gtk import gdk
+import gobject
+
+gdk.threads_init ()
+
+import sys
+import re
+import os
+import traceback
+from Queue import Queue
+import threading
+
+from reportbug.exceptions import NoPackage, NoBugs, NoNetwork, NoReport
+from reportbug import debianbts
+from reportbug.urlutils import launch_browser
+
+ISATTY = True
+
+# Utilities
+
+def highlight (s):
+    return '<b>%s</b>' % s
+
+re_markup_free = re.compile ("<.*?>")
+
+def markup_free (s):
+    return re_markup_free.sub ("", s)
+
+def ask_free (s):
+    s = s.strip ()
+    if s[-1] in ('?', ':'):
+        return s[:-1]
+    return s
+
+def create_scrollable (widget):
+    scrolled = gtk.ScrolledWindow ()
+    scrolled.set_shadow_type (gtk.SHADOW_ETCHED_IN)
+    scrolled.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+    scrolled.add (widget)
+    return scrolled
+
+def info_dialog (message):
+    dialog = gtk.MessageDialog (assistant, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                                gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, message)
+    dialog.connect ('response', lambda *args: dialog.destroy ())
+    dialog.set_title ('Reportbug')
+    dialog.show_all ()
+
+def error_dialog (message):
+    dialog = gtk.MessageDialog (assistant, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                                gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, message)
+    dialog.connect ('response', lambda *args: dialog.destroy ())
+    dialog.set_title ('Reportbug')
+    dialog.show_all ()
+
+class ExceptionDialog (gtk.Dialog):
+    # Register an exception hook to display an error when the GUI breaks
+    @classmethod
+    def create_excepthook (cls, oldhook):
+        def excepthook (exctype, value, tb):
+            if oldhook:
+                oldhook (exctype, value, tb)
+            application.run_once_in_main_thread (cls.start_dialog,
+                                                 ''.join (traceback.format_exception (exctype, value, tb)))
+        return excepthook
+
+    @classmethod
+    def start_dialog (cls, tb):
+        try:
+            dialog = cls (tb)
+            dialog.show_all ()
+        except:
+            sys.exit (1)
+
+    def __init__ (self, tb):
+        gtk.Dialog.__init__ (self, "Reportbug: exception", assistant,
+                             gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                             (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
+        # Try following the HIG
+        self.set_border_width (5)
+
+        vbox = gtk.VBox (spacing=10)
+        vbox.set_border_width (6)
+        self.vbox.pack_start (vbox)
+        
+        # The header image + label
+        hbox = gtk.HBox (spacing=10)
+        vbox.pack_start (hbox, expand=False)
+
+        align = gtk.Alignment (0.5, 0.5, 1.0, 1.0)
+        hbox.pack_start (align, expand=False)
+
+        image = gtk.image_new_from_stock (gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_DIALOG)
+        hbox.pack_start (image)
+        
+        label = gtk.Label ("An error has occurred while doing an operation in Reportbug.\nPlease report the bug.")
+        label.set_line_wrap (True)
+        label.set_justify (gtk.JUSTIFY_FILL)
+        hbox.pack_start (label, expand=False)
+
+        # The traceback
+        expander = gtk.Expander ("More details")
+        vbox.pack_start (expander)
+
+        view = gtk.TextView ()
+        view.set_editable (False)
+        view.get_buffer().set_text (tb)
+        scrolled = create_scrollable (view)
+        expander.add (scrolled)
+
+        self.connect ('response', self.on_response)
+
+    def on_response (self, dialog, res):
+        sys.exit (1)
+
+oldhook = sys.excepthook
+sys.excepthook = ExceptionDialog.create_excepthook (oldhook)
+
+# BTS
+
+class Bug (object):
+    def __init__ (self, raw):
+        # Skip the '#'
+        raw = raw[1:]
+        bits = re.split(r'[: ]', raw, 2)
+        self.id, self.tag, self.data = bits
+        # Remove [ and ]
+        self.tag = self.tag[1:-1]
+        self.data = self.data.strip ()
+        self.package = self.data.split(']', 1)[0][1:]
+
+        self.reporter = self.get_data ("Reported by:")
+        self.date = self.get_data ("Date:")
+        self.severity = self.get_data("Severity:").capitalize ()
+        self.version = self.get_data ("Found in version")
+        self.filed_date = self.get_data ("Filed")
+        self.modified_date = self.get_data ("Modified")
+
+        # Get rid of [package] which has been stored in self.package
+        self.info = self.data.split(']', 1)[1]
+        self.info = self.info[:self.info.lower().index ("reported by:")].strip ()
+        if not self.info:
+            self.info = '(no subject)'   
+
+    def get_data (self, token):
+        info = ''
+        try:
+            index = self.data.lower().index (token.lower ())
+        except:
+            return '(unknown)'
+
+        i = index + len(token)
+        while True:
+            c = self.data[i]
+            if c == ';':
+                break
+            info += c
+            i += 1
+        return info.strip ()
+
+    def __iter__ (self):
+        yield self.id
+        yield self.tag
+        yield self.package
+        yield self.info
+        yield self.reporter
+        yield self.date
+        yield self.severity
+        yield self.version
+        yield self.filed_date
+        yield self.modified_date
+
+class BugReport (object):
+    def __init__ (self, message):
+        lines = message.split ('\n')
+        i = 0
+
+        self.headers = []
+        while i < len (lines):
+            line = lines[i]
+            i += 1
+            if not line.strip ():
+                break
+            self.headers.append (line)
+
+        store = 0
+        info = []
+        while i < len (lines):
+            line = lines[i]
+            info.append (line)
+            i += 1
+            if store < 2 and not line.strip():
+                store += 1
+                continue
+            if store == 2 and (line.startswith ('-- ') or line.startswith ('** ')):
+                break
+            store = 0
+        self.original_info = '\n'.join (info[:-3])
+
+        self.others = '\n'.join (lines[i-1:])
+
+    def get_others (self):
+        return self.others
+
+    def get_original_info (self):
+        return self.original_info
+
+    def get_subject (self):
+        for header in self.headers:
+            if 'Subject' in header:
+                return header[len ('Subject: '):]
+
+    def set_subject (self, subject):
+        for i in range (len (self.headers)):
+            if 'Subject' in self.headers[i]:
+                self.headers[i] = 'Subject: '+subject
+                break
+
+    def create_message (self, info):
+        message = """%s
+
+%s
+
+
+%s""" % ('\n'.join (self.headers), info,self.others)
+        return message
+
+# BTS GUI
+
+class BugPage (gtk.EventBox, threading.Thread):
+    def __init__ (self, dialog, number, bts, mirrors, http_proxy, archived):
+        threading.Thread.__init__ (self)
+        gtk.EventBox.__init__ (self)
+        self.dialog = dialog
+        self.assistant = self.dialog.assistant
+        self.application = self.assistant.application
+        self.number = number
+        self.bts = bts
+        self.mirrors = mirrors
+        self.http_proxy = http_proxy
+        self.archived = archived
+
+        vbox = gtk.VBox (spacing=12)
+        vbox.pack_start (gtk.Label ("Retrieving bug information."), expand=False)
+
+        self.progress = gtk.ProgressBar ()
+        self.progress.set_pulse_step (0.01)
+        vbox.pack_start (self.progress, expand=False)
+
+        self.add (vbox)
+
+    def run (self):
+        # Start the progress bar
+        gobject.timeout_add (10, self.pulse)
+
+        info = debianbts.get_report (int (self.number), self.bts, mirrors=self.mirrors,
+                                     http_proxy=self.http_proxy, archived=self.archived)
+        if not info:
+            self.application.run_once_in_main_thread (self.not_found)
+        else:
+            self.application.run_once_in_main_thread (self.found, info)
+
+    def drop_progressbar (self):
+        child = self.get_child ()
+        self.remove (child)
+        child.unparent ()
+
+    def pulse (self):
+        self.progress.pulse ()
+        return self.isAlive ()
+
+    def not_found (self):
+        self.drop_progressbar ()
+        self.add (gtk.Label ("The bug can't be fetched or it doesn't exist."))
+        self.show_all ()
+        
+    def found (self, info):
+        self.drop_progressbar ()
+        desc = info[0].split(':', 1)[1].strip ()
+        bodies = info[1]
+        vbox = gtk.VBox (spacing=12)
+        vbox.set_border_width (12)
+        vbox.pack_start (gtk.Label ('Description: '+desc), expand=False)
+        
+        view = gtk.TreeView ()
+        view.get_selection().set_mode (gtk.SELECTION_NONE)
+        view.set_rules_hint (True)
+        model = gtk.ListStore (str)
+        view.set_model (model)
+        view.append_column (gtk.TreeViewColumn ('Replies', gtk.CellRendererText (), text=0))
+        for body in bodies:
+            model.append ([body])
+        scrolled = create_scrollable (view)
+        vbox.pack_start (scrolled)
+
+        bbox = gtk.HButtonBox ()
+        button = gtk.Button ("Open in browser")
+        button.connect ('clicked', self.on_open_browser)
+        bbox.pack_start (button)
+        button = gtk.Button ("Reply")
+        button.set_image (gtk.image_new_from_stock (gtk.STOCK_EDIT, gtk.ICON_SIZE_BUTTON))
+        button.connect ('clicked', self.on_reply)
+        bbox.pack_start (button)
+        vbox.pack_start (bbox, expand=False)
+
+        self.add (vbox)
+        self.show_all ()
+            
+    def on_open_browser (self, button):
+        launch_browser (debianbts.get_report_url (self.bts, int (self.number), self.archived))
+
+    def on_reply (self, button):
+        # Return the bug number to reportbug
+        self.application.set_next_value (self.number)
+        # Forward the assistant to the progress bar
+        self.assistant.forward_page ()
+        # Though we only a page, we are authorized to destroy our parent :)
+        self.dialog.destroy ()
+
+class BugsDialog (gtk.Dialog):
+    def __init__ (self, assistant):
+        gtk.Dialog.__init__ (self, "Reportbug: bug information", assistant,
+                             gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                             (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
+        self.assistant = assistant
+        self.application = assistant.application
+        self.notebook = gtk.Notebook ()
+        self.vbox.pack_start (self.notebook)
+        self.connect ('response', self.on_response)
+
+    def on_response (self, *args):
+        self.destroy ()
+
+    def show_bug (self, number, *args):
+        page = BugPage (self, number, *args)
+        self.notebook.append_page (page, gtk.Label (number))
+        page.start ()
+        
+# Application
+
+class ReportbugApplication (threading.Thread):
+    def __init__ (self):
+        threading.Thread.__init__ (self)
+        self.queue = Queue ()
+        self.next_value = None
+        
+    def run (self):
+        gtk.main ()
+
+    def get_last_value (self):
+        return self.queue.get ()
+
+    def put_next_value (self):
+        self.queue.put (self.next_value)
+        self.next_value = None
+    
+    def set_next_value (self, value):
+        self.next_value = value
+
+    @staticmethod
+    def create_idle_callback (func, *args, **kwargs):
+        def callback ():
+            func (*args, **kwargs)
+            return False
+        return callback
+
+    def run_once_in_main_thread (self, func, *args, **kwargs):
+        gobject.idle_add (self.create_idle_callback (func, *args, **kwargs))
+
+application = ReportbugApplication ()
+
+# Connection with reportbug
+
+# Syncronize "pipe" with reportbug
+
+class SyncReturn (RuntimeError):
+    def __init__ (self, result):
+        RuntimeError.__init__ (result)
+        self.result = result
+
+class ReportbugConnector (object):
+    # Executed in the glib thread
+    def execute_operation (self, *args, **kwargs):
+        pass
+
+    # Executed in sync with reportbug. raise SyncResult (value) to directly return to reportbug
+    # Returns args and kwargs to pass to execute_operation
+    def sync_pre_operation (cls, *args, **kwargs):
+        return args, kwargs
+        
+# Assistant
+
+class Page (ReportbugConnector):
+    next_page_num = 0
+    page_type = gtk.ASSISTANT_PAGE_CONTENT
+    default_complete = False
+    side_image = "/usr/share/pixmaps/debian-logo.png"
+
+    def __init__ (self, assistant):
+        self.assistant = assistant
+        self.application = assistant.application
+        self.widget = self.create_widget ()
+        self.widget.page = self
+        self.widget.set_border_width (6)
+        self.widget.show_all ()
+        self.page_num = Page.next_page_num
+        Page.next_page_num += 1
+
+    def execute_operation (self, *args, **kwargs):
+        self.switch_in ()
+        self.connect_signals ()
+        self.execute (*args, **kwargs)
+        self.assistant.show ()
+
+    def connect_signals (self):
+        pass
+
+    def set_page_complete (self, complete):
+        self.assistant.set_page_complete (self.widget, complete)
+
+    def set_page_type (self, type):
+        self.assistant.set_page_type (self.widget, type)
+
+    def set_page_title (self, title):
+        if title:
+            self.assistant.set_page_title (self.widget, title)
+
+    # The user will see this as next page
+    def switch_in (self):
+        self.assistant.insert_page (self.widget, self.page_num)
+        self.set_page_complete (self.default_complete)
+        self.set_page_type (self.page_type)
+        self.assistant.set_page_side_image (self.widget, gdk.pixbuf_new_from_file (self.side_image))
+        self.assistant.set_next_page (self)
+        self.set_page_title ("Reportbug")
+
+    # The user forwarded the assistant to see the next page
+    def switch_out (self):
+        pass
+
+    def is_valid (self, value):
+        return bool (value)
+
+    def validate (self, *args, **kwargs):
+        value = self.get_value ()
+        if self.is_valid (value):
+            self.assistant.application.set_next_value (value)
+            self.set_page_complete (True)
+        else:
+            self.set_page_complete (False)
+
+class IntroPage (Page):
+    page_type = gtk.ASSISTANT_PAGE_INTRO
+    default_complete = True
+
+    def create_widget (self):
+        vbox = gtk.VBox ()
+        vbox.pack_start (gtk.Label ("ReportBUG"))
+        return vbox
+
+class GetStringPage (Page):
+    def create_widget (self):
+        vbox = gtk.VBox (spacing=12)
+        self.label = gtk.Label ()
+        self.label.set_line_wrap (True)
+        self.entry = gtk.Entry ()
+        vbox.pack_start (self.label, expand=False)
+        vbox.pack_start (self.entry, expand=False)
+        return vbox
+
+    def connect_signals (self):
+        self.entry.connect ('changed', self.validate)
+
+    def get_value (self):
+        return self.entry.get_text ()
+
+    def execute (self, prompt, options=None, force_prompt=False, default=''):
+        self.label.set_text (prompt)
+        self.entry.grab_focus ()
+
+class TreePage (Page):
+    value_column = None
+
+    def __init__ (self, *args, **kwargs):
+        Page.__init__ (self, *args, **kwargs)
+        self.selection = self.view.get_selection()
+
+    def connect_signals (self):
+        self.selection.connect ('changed', self.validate)
+
+    def get_value (self):
+        model, paths = self.selection.get_selected_rows ()
+        multiple = self.selection.get_mode () == gtk.SELECTION_MULTIPLE
+        result = []
+        for path in paths:
+            value = model.get_value (model.get_iter (path), self.value_column)
+            if value is not None:
+                result.append (markup_free (value))
+        if result and not multiple:
+            return result[0]
+        return result
+
+class MenuPage (TreePage):
+    value_column = 0
+
+    def create_widget (self):
+        vbox = gtk.VBox (spacing=12)
+        self.label = gtk.Label ()
+        vbox.pack_start (self.label, expand=False)
+
+        self.view = gtk.TreeView ()
+        self.view.set_rules_hint (True)
+        scrolled = create_scrollable (self.view)
+        vbox.pack_start (scrolled)
+        vbox.show_all ()
+        return vbox
+
+    def is_valid (self, value):
+        if self.empty_ok:
+            return True
+        else:
+            return bool (value)
+
+    def execute (self, par, options, prompt, default=None, any_ok=False,
+                 order=None, extras=None, multiple=False, empty_ok=False):
+        self.empty_ok = empty_ok
+
+        self.label.set_text (par)
+
+        self.model = gtk.ListStore (str, str)
+        self.view.set_model (self.model)
+
+        if multiple:
+            self.selection.set_mode (gtk.SELECTION_MULTIPLE)
+
+        self.view.append_column (gtk.TreeViewColumn ('Option', gtk.CellRendererText (), markup=0))
+        self.view.append_column (gtk.TreeViewColumn ('Description', gtk.CellRendererText (), text=1))
+
+        default_iter = None
+        if isinstance (options, dict):
+            for option, desc in options.iteritems ():
+                iter = self.model.append ((highlight (option), desc))
+                if option == default:
+                    default_iter = iter
+        else:
+            for row in options:
+                iter = self.model.append ((highlight (row[0]), row[1]))
+                if row[0] == default:
+                    default_iter = iter
+
+        if default_iter:
+            self.selection.select_iter (default_iter)
+
+class HandleBTSQueryPage (TreePage):
+    default_complete = True
+    value_column = 0
+
+    def sync_pre_operation (self, package, bts, mirrors=None, http_proxy="", queryonly=False, screen=None,
+                            archived='no', source=False, version=None):
+        self.bts = bts
+        self.mirrors = mirrors
+        self.http_proxy = http_proxy
+        self.archived = archived
+
+        sysinfo = debianbts.SYSTEMS[bts]
+        root = sysinfo.get('btsroot')
+        if not root:
+            # do we need to make a dialog for this?
+            return
+
+        if isinstance(package, basestring):
+            pkgname = package
+            if source:
+                pkgname += ' (source)'
+
+            progress_label = 'Querying %s bug tracking system for reports on %s' % (debianbts.SYSTEMS[bts]['name'], pkgname)
+        else:
+            progress_label = 'Querying %s bug tracking system for reports %s' % (debianbts.SYSTEMS[bts]['name'], ' '.join([str(x) for x in package]))
+
+
+        self.application.run_once_in_main_thread (self.assistant.set_progress_label, progress_label)
+
+        result = None
+        try:
+            (count, sectitle, hierarchy) = debianbts.get_reports (
+                package, bts, mirrors=mirrors, version=version,
+                http_proxy=http_proxy, archived=archived, source=source)
+
+            if not count:
+                if hierarchy == None:
+                    raise NoPackage
+                else:
+                    raise NoBugs
+            else:
+                if count > 1:
+                    sectitle = '%d bug reports found' % (count,)
+                else:
+                    sectitle = 'One bug report found'
+
+                report = []
+                for category, bugs in hierarchy:
+                    buglist = []
+                    for bug in bugs:
+                        buglist.append (Bug (bug))
+                    report.append ((category, buglist))
+
+                return (report, sectitle), {}
+
+        except (IOError, NoNetwork):
+            error_dialog ("Unable to connect to %s BTS." % sysinfo['name'])
+        except NoPackage:
+            error_dialog ('No record of this package found.')
+            raise NoPackage
+
+        if result and result < 0:
+            raise NoReport
+
+        raise SyncReturn (result)
+
+    def create_widget (self):
+        vbox = gtk.VBox (spacing=12)
+        self.label = gtk.Label ("List of bugs. Select a bug to retrieve and submit more information.")
+        vbox.pack_start (self.label, expand=False)
+
+        self.view = gtk.TreeView ()
+        self.view.set_rules_hint (True)
+        scrolled = create_scrollable (self.view)
+        vbox.pack_start (scrolled)
+
+        button = gtk.Button ("Retrieve and submit bug information")
+        button.set_image (gtk.image_new_from_stock (gtk.STOCK_INFO, gtk.ICON_SIZE_BUTTON))
+        button.connect ('clicked', self.on_retrieve_info)
+        vbox.pack_start (button, expand=False)
+        return vbox
+
+    def connect_signals (self):
+        TreePage.connect_signals (self)
+        self.view.connect ('row-activated', self.on_retrieve_info)
+
+    def on_retrieve_info (self, *args):
+        bug_ids = TreePage.get_value (self)
+        if not bug_ids:
+            info_dialog ("Please select one ore more bugs")
+            return
+
+        dialog = BugsDialog (self.assistant)
+        for id in bug_ids:
+            dialog.show_bug (id, self.bts, self.mirrors, self.http_proxy, self.archived)
+        dialog.show_all ()
+
+    def is_valid (self, value):
+        return True
+
+    def get_value (self):
+        # The value returned to reportbug doesn't depend by a selection, but by the dialog of a bug
+        return None
+
+    def execute (self, buglist, sectitle):
+        self.label.set_text ("%s. Double-click a bug to retrieve and submit more information." % sectitle)
+
+        columns = ['ID', 'Tag', 'Package', 'Description', 'Reporter', 'Date', 'Severity', 'Version',
+                   'Filed date', 'Modified date']
+
+        self.model = gtk.TreeStore (*([str] * len (columns)))
+        self.view.set_model (self.model)
+
+        for col in zip (columns, range (len (columns))):
+            self.view.append_column (gtk.TreeViewColumn (col[0], gtk.CellRendererText (), text=col[1]))
+
+        for category in buglist:
+            row = [None] * len (columns)
+            row[3] = category[0]
+            iter = self.model.append (None, row)
+            for bug in category[1]:
+                self.model.append (iter, list (bug))
+
+        self.selection.set_mode (gtk.SELECTION_MULTIPLE)
+
+class DisplayReportPage (Page):
+    default_complete = True
+
+    def create_widget (self):
+        self.view = gtk.TextView ()
+        self.view.set_editable (False)
+        scrolled = create_scrollable (self.view)
+        return scrolled
+
+    def execute (self, message, *args):
+        self.view.get_buffer().set_text (message % args)
+
+class LongMessagePage (Page):
+    default_complete = True
+    
+    def create_widget (self):
+        self.label = gtk.Label ()
+        eb = gtk.EventBox ()
+        eb.add (self.label)
+        return eb
+
+    def execute (self, message, *args):
+        message = message % args
+        self.label.set_text (message)
+        # Reportbug should use final_message, so emulate it
+        if ('999999' in message):
+            self.set_page_type (gtk.ASSISTANT_PAGE_CONFIRM)
+            self.set_page_title ("Thanks for your report")
+
+class FinalMessagePage (LongMessagePage):
+    page_type = gtk.ASSISTANT_PAGE_CONFIRM
+    default_complete = True
+
+    def execute (self, *args, **kwargs):
+        LongMessagePage.execute (self, *args, **kwargs)
+        self.set_page_title ("Thanks for your report")
+
+class GetMultilinePage (Page):
+    default_complete = True
+
+    def create_widget (self):
+        vbox = gtk.VBox (spacing=12)
+        self.label = gtk.Label ()
+        vbox.pack_start (self.label, expand=False)
+
+        view = gtk.TextView ()
+        self.buffer = view.get_buffer ()
+        scrolled = create_scrollable (view)
+        vbox.pack_start (scrolled)
+        return vbox
+
+    def is_valid (self, value):
+        return True
+
+    def connect_signals (self):
+        self.buffer.connect ('changed', self.validate)
+
+    def get_value (self):
+        return self.buffer.get_text (self.buffer.get_start_iter (), self.buffer.get_end_iter ())
+
+    def execute (self, prompt):
+        self.label.set_text (prompt)
+
+class EditorPage (Page):
+    def create_widget (self):
+        vbox = gtk.VBox (spacing=6)
+        hbox = gtk.HBox (spacing=12)
+        hbox.pack_start (gtk.Label ("Subject: "), expand=False)
+        self.subject = gtk.Entry ()
+        hbox.pack_start (self.subject)
+        vbox.pack_start (hbox, expand=False)
+
+        self.view = gtk.TextView ()
+        self.info_buffer = self.view.get_buffer ()
+        scrolled = create_scrollable (self.view)
+        vbox.pack_start (scrolled)
+
+        expander = gtk.Expander ("Other system information")
+        view = gtk.TextView ()
+        view.set_editable (False)
+        self.others_buffer = view.get_buffer ()
+        scrolled = create_scrollable (view)
+        expander.add (scrolled)
+        vbox.pack_start (expander)
+        return vbox
+
+    def switch_out (self):
+        f = file (self.filename, "w")
+        f.write (self.get_value()[0])
+        f.close ()
+
+    def connect_signals (self):
+        self.info_buffer.connect ('changed', self.validate)
+        self.subject.connect ('changed', self.validate)
+
+    def get_value (self):
+        info = self.info_buffer.get_text (self.info_buffer.get_start_iter (),
+                                          self.info_buffer.get_end_iter ())
+        if not info.strip ():
+            return None
+        subject = self.subject.get_text().strip ()
+        if not subject.strip ():
+            return None
+
+        self.report.set_subject (subject)
+        message = self.report.create_message (info)
+        message = message.decode (self.charset, 'replace')
+        return (message, message != self.message)
+
+    def handle_first_info (self):
+        self.focus_in_id = self.view.connect ('focus-in-event', self.on_view_focus_in_event)
+
+    def on_view_focus_in_event (self, view, *args):
+        # Empty the buffer only the first time
+        self.info_buffer.set_text ("")
+        view.disconnect (self.focus_in_id)
+
+    def execute (self, message, filename, editor, charset='utf-8'):
+        self.message = message
+        self.report = BugReport (message)
+        self.filename = filename
+        self.charset = charset
+        self.subject.set_text (self.report.get_subject ())
+        self.others_buffer.set_text (self.report.get_others ())
+
+        info = self.report.get_original_info ()
+        if info.strip () == "*** Please type your report below this line ***":
+            info = "Please type your report here..."
+            self.handle_first_info ()
+        self.info_buffer.set_text (info)
+
+class SelectOptionsPage (Page):
+    default_complete = True
+
+    def create_widget (self):
+        self.label = gtk.Label ()
+        self.vbox = gtk.VBox (spacing=6)
+        self.vbox.pack_start (self.label, expand=False, padding=6)
+        return self.vbox
+
+    def on_clicked (self, button, menuopt):
+        self.application.set_next_value (menuopt)
+        self.assistant.forward_page ()
+
+    def execute (self, prompt, menuopts, options):
+        self.label.set_text (prompt)
+
+        default = None
+        buttons = []
+        for menuopt in menuopts:
+            desc = options[menuopt.lower ()]
+            # do we really need to launch an external editor?
+            if 'Change editor' in desc:
+                continue
+            button = gtk.Button (options[menuopt.lower ()])
+            button.connect ('clicked', self.on_clicked, menuopt)
+            if menuopt.isupper ():
+                default = button
+                buttons.insert (0, gtk.HSeparator ())
+                buttons.insert (0, button)
+            else:
+                buttons.append (button)
+
+        for button in buttons:
+            self.vbox.pack_start (button, expand=False)
+
+        if default:
+            default.set_flags (gtk.CAN_DEFAULT | gtk.HAS_DEFAULT)
+            default.grab_default ()
+            default.grab_focus ()
+
+        self.vbox.show_all ()
+
+class ProgressPage (Page):
+    page_type = gtk.ASSISTANT_PAGE_PROGRESS
+
+    def pulse (self):
+        self.progress.pulse ()
+        return True
+
+    def create_widget (self):
+        vbox = gtk.VBox (spacing=6)
+        self.label = gtk.Label ()
+        self.progress = gtk.ProgressBar ()
+        self.progress.set_pulse_step (0.01)
+        vbox.pack_start (self.label, expand=False)
+        vbox.pack_start (self.progress, expand=False)
+        gobject.timeout_add (10, self.pulse)
+        return vbox
+
+    def set_label (self, text):
+        self.label.set_text (text)
+
+    def reset_label (self):
+        self.set_label ("This operation may take a while")
+
+class ReportbugAssistant (gtk.Assistant):
+    def __init__ (self, application):
+        gtk.Assistant.__init__ (self)
+        self.set_title ('Reportbug')
+        self.application = application
+        self.showing_page = None
+        self.requested_page = None
+        self.progress_page = None
+        self.set_forward_page_func (self.forward)
+        self.connect_signals ()
+        self.setup_pages ()
+
+    def connect_signals (self):
+        self.connect ('cancel', self.close)
+        self.connect ('prepare', self.on_prepare)
+        self.connect ('delete-event', self.close)
+        self.connect ('apply', self.close)
+        
+    def on_prepare (self, assistant, widget):
+        # If the user goes back then forward, we must ensure the feedback value to reportbug must be sent
+        # when the user clicks on "Forward" to the requested page by reportbug
+        if self.showing_page and self.showing_page == self.requested_page and self.get_current_page () > self.showing_page.page_num:
+            self.application.put_next_value ()
+            # Reportbug doesn't support going back, so make widgets insensitive
+            self.showing_page.widget.set_sensitive (False)
+            self.showing_page.switch_out ()
+
+        self.showing_page = widget.page
+        # Some pages might have changed the label in the while
+        if self.showing_page == self.progress_page:
+            self.progress_page.reset_label ()
+
+    def close (self, *args):
+        sys.exit (0)
+        
+    def forward (self, page_num):
+        return page_num + 1
+
+    def forward_page (self):
+        self.set_current_page (self.forward (self.showing_page.page_num))
+
+    def set_next_page (self, page):
+        self.requested_page = page
+        # If we're in progress immediately show this guy
+        if self.showing_page == self.progress_page:
+            self.set_current_page (page.page_num)
+
+    def set_progress_label (self, text, *args, **kwargs):
+        self.progress_page.set_label (text % args)
+
+    def setup_pages (self):
+        # We insert pages between the intro and the progress, so that we give the user the feedback
+        # that the applications is still running when he presses the "Forward" button
+        self.showing_page = IntroPage (self)
+        self.progress_page = ProgressPage (self)
+        Page.next_page_num = 1
+        self.showing_page.switch_in ()
+        self.progress_page.switch_in ()
+        self.set_current_page (0)
+
+assistant = ReportbugAssistant (application)
+
+
+# Dialogs
+
+
+class YesNoDialog (ReportbugConnector, gtk.MessageDialog):
+    def __init__ (self, application):
+        gtk.MessageDialog.__init__ (self, assistant, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                                    gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO)
+        self.application = application
+        self.connect ('response', self.on_response)
+
+    def on_response (self, dialog, res):
+        self.application.set_next_value (res == gtk.RESPONSE_YES)
+        self.application.put_next_value ()
+        self.destroy ()
+
+    def execute_operation (self, msg, yeshelp=None, nohelp=None, default=True, nowrap=False):
+        self.set_markup (msg+"?")
+        self.show_all ()
+
+class DisplayFailureDialog (ReportbugConnector, gtk.MessageDialog):
+    def __init__ (self, application):
+        gtk.MessageDialog.__init__ (self, assistant, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                                    gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE)
+        self.application = application
+        self.connect ('response', self.on_response)
+
+    def on_response (self, dialog, res):
+        self.application.put_next_value ()
+        self.destroy ()
+
+    def execute_operation (self, msg):
+        self.set_markup (msg)
+        self.show_all ()
+             
+class GetFilenameDialog (ReportbugConnector, gtk.FileChooserDialog):
+    def __init__ (self, application):
+        gtk.FileChooserDialog.__init__ (self, '', assistant, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                                                                      gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+        self.application = application
+        self.connect ('response', self.on_response)
+
+    def on_response (self, dialog, res):
+        value = None
+        if res == gtk.RESPONSE_OK:
+            value = self.get_filename ()
+
+        self.application.set_next_value (value)
+        self.application.put_next_value ()
+        self.destroy ()
+
+    def execute_operation (self, title, force_prompt=False):
+        self.set_title (ask_free (title))
+        self.show_all ()
+   
+log_message = assistant.set_progress_label
+
+def select_multiple (*args, **kwargs):
+    kwargs['multiple'] = True
+    kwargs['empty_ok'] = True
+    return menu (*args, **kwargs)
+
+pages = { 'get_string': GetStringPage,
+          'menu': MenuPage,
+          'handle_bts_query': HandleBTSQueryPage,
+          'long_message': LongMessagePage,
+          'display_report': DisplayReportPage,
+          'final_message': FinalMessagePage,
+          'spawn_editor': EditorPage,
+          'select_options': SelectOptionsPage }
+dialogs = { 'yes_no': YesNoDialog,
+            'get_filename': GetFilenameDialog,
+            'display_failure': DisplayFailureDialog, }
+
+# Begin the circle
+
+application.start ()
+
+def create_forwarder (parent, klass):
+    def func (*args, **kwargs):
+        op = klass (parent)
+        try:
+            args, kwargs = op.sync_pre_operation (*args, **kwargs)
+        except SyncReturn, e:
+            return e.result
+        application.run_once_in_main_thread (op.execute_operation, *args, **kwargs)
+        return application.get_last_value ()
+    return func
+      
+def forward_operations (parent, operations):
+    for operation, klass in operations.iteritems ():
+        globals()[operation] = create_forwarder (parent, klass)
+
+forward_operations (assistant, pages)
+forward_operations (application, dialogs)
+
+def test ():
+    # Write some tests here
+    asd ()
+
+if __name__ == '__main__':
+    test ()
+
+


Property changes on: trunk/reportbug/ui/gtk2_ui.py
___________________________________________________________________
Name: svn:mergeinfo
   + 




More information about the Reportbug-commits mailing list