[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