-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
On Sat, 30 Aug 2008 01:49:26 +0200
Luca Bruno <[EMAIL PROTECTED]> wrote:
> Continued the work, as usual I've attached the .py file.
> It's almost complete for basic bug reporting.
> Please let me know what you think about.
>
The frontend is now almost complete. It's missing the wrapper for querying a
single report and submit more informations.
I've added the copyright.
I'm now trying to get it working in trunk.
Best regards,
- --
http://syx.googlecode.com - Smalltalk YX
http://lethalman.blogspot.com - Thoughts about computer technologies
http://www.ammazzatecitutti.org - Ammazzateci tutti
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
iEYEARECAAYFAki5080ACgkQw9Qj+8Kak3FXIgCdEWon06/x62tweYtqxGuXLZUH
Yg4AoJBioO1ds7djAWP4cagUAnejkUmz
=LHID
-----END PGP SIGNATURE-----
# reportbuglib/reportbug_ui_gnome2.py
# GTK+ user interface for reportbug
# Written by Luca Bruno <[EMAIL PROTECTED]>
# Based on gnome-reportbug work done by Philipp Kern <[EMAIL PROTECTED]>
# 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
import sys
from gtk import gdk
import gobject
import re
import os
gdk.threads_init ()
from Queue import Queue
import threading
if __name__ == '__main__':
sys.path.append ('..')
from reportbug_ui_text import ewrite
from reportbug_exceptions import NoPackage, NoBugs, NoNetwork, NoReport
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
class ErrorDialog (gtk.MessageDialog):
def __init__ (self, application):
gtk.MessageDialog.__init__ (self, assistant, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
self.application = application
self.connect ('response', self.on_response)
def on_response (self, dialog, res):
self.destroy ()
def execute_operation (self, msg, yeshelp, nohelp, default=True, nowrap=False, ui=None):
self.set_markup (msg+"?")
self.show_all ()
def error_dialog (message):
dialog = gtk.MessageDialog (self, assistant, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, essage)
dialog.connect ('response', lambda *args: dialog.destroy)
dialog.set_title ('Reportbug')
dialog.show_all ()
# 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.data.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
# Application
class ReportbugApplication (threading.Thread):
def __init__ (self):
threading.Thread.__init__ (self)
self.queue = Queue ()
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 SyncError (RuntimeError):
def __init__ (self, result):
RuntimeError.__init__ ()
self.result = result
class ReportbugConnector (object):
def execute_operation (self, *args, **kwargs):
pass
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)
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:
result.append (markup_free (model.get_value (model.get_iter (path), self.value_column)))
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 ()
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):
import debianbts
sysinfo = debianbts.SYSTEMS[bts]
root = sysinfo.get('btsroot')
if not root:
ewrite("%s bug tracking system has no web URL; bypassing query.\n",
sysinfo['name'])
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 informations.")
vbox.pack_start (self.label, expand=False)
self.view = gtk.TreeView ()
scrolled = create_scrollable (self.view)
vbox.pack_start (scrolled)
return vbox
def is_valid (self, value):
return True
def get_value (self):
return None
def execute (self, buglist, sectitle):
self.label.set_text ("%s. Double-click a bug to retrieve and submit more informations." % 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))
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)
view = gtk.TextView ()
self.info_buffer = view.get_buffer ()
scrolled = create_scrollable (view)
vbox.pack_start (scrolled)
expander = gtk.Expander ("Other system informations")
view = gtk.TextView ()
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 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.info_buffer.set_text (self.report.get_original_info ())
self.others_buffer.set_text (self.report.get_others ())
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.set_current_page (self.assistant.forward (self.page_num))
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 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)
assistant.show_all ()
# 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, default=True, nowrap=False):
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
display_failure = ewrite
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 }
# 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 SyncError, 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 ():
print "Write some tests here"
if __name__ == '__main__':
test ()
_______________________________________________
Reportbug-maint mailing list
[email protected]
http://lists.alioth.debian.org/mailman/listinfo/reportbug-maint