12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598 |
- # a graphical (GTK+) user interface
- # Written by Luca Bruno <lethalman88@gmail.com>
- # Based on gnome-reportbug work done by Philipp Kern <pkern@debian.org>
- # Copyright (C) 2006 Philipp Kern
- # Copyright (C) 2008-2009 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.
- try:
- import gtk
- import gobject
- import pango
- except ImportError:
- raise UINotImportable('Please install the python-gtk2 package to use this interface.')
- global vte
- try:
- import gtkspellcheck
- has_spell = True
- except:
- has_spell = False
- gtk.set_interactive(0)
- gtk.gdk.threads_init()
- import sys
- import re
- import os
- import traceback
- from Queue import Queue
- import threading
- import textwrap
- from reportbug.exceptions import NoPackage, NoBugs, NoNetwork, NoReport
- from reportbug import debbugs
- from reportbug.urlutils import launch_browser
- ISATTY = True
- DEBIAN_LOGO = "/usr/share/pixmaps/debian-logo.png"
- global application, assistant, report_message
- # 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, with_viewport=False):
- scrolled = gtk.ScrolledWindow()
- scrolled.set_shadow_type(gtk.SHADOW_ETCHED_IN)
- scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
- if with_viewport:
- scrolled.add_with_viewport(widget)
- else:
- 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 d, *args: d.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 d, *args: d.destroy())
- dialog.set_title('Reportbug')
- dialog.show_all()
- class CustomDialog(gtk.Dialog):
- def __init__(self, stock_image, message, buttons, *args, **kwargs):
- gtk.Dialog.__init__(self, "Reportbug", assistant,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- buttons)
- # Try following the HIG
- self.set_default_response(buttons[-1]) # this is the response of the last button
- 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(stock_image, gtk.ICON_SIZE_DIALOG)
- hbox.pack_start(image)
- label = gtk.Label(message)
- label.set_line_wrap(True)
- label.set_justify(gtk.JUSTIFY_FILL)
- label.set_selectable(True)
- label.set_property("can-focus", False)
- hbox.pack_start(label, expand=False)
- self.setup_dialog(vbox, *args, **kwargs)
- class InputStringDialog(CustomDialog):
- def __init__(self, message):
- CustomDialog.__init__(self, gtk.STOCK_DIALOG_INFO, message,
- (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
- gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
- def setup_dialog(self, vbox):
- self.entry = gtk.Entry()
- vbox.pack_start(self.entry, expand=False)
- def get_value(self):
- return self.entry.get_text()
- class ExceptionDialog(CustomDialog):
- # 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):
- CustomDialog.__init__(self, gtk.STOCK_DIALOG_ERROR, "An error has occurred while doing an operation in Reportbug.\nPlease report the bug.", (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), tb)
- def setup_dialog(self, vbox, tb):
- # The traceback
- expander = gtk.Expander("More details")
- vbox.pack_start(expander, True)
- 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)
- class ReportViewerDialog(gtk.Dialog):
- def __init__(self, message):
- gtk.Dialog.__init__(self, "Reportbug", assistant,
- gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- (gtk.STOCK_COPY, gtk.RESPONSE_APPLY,
- gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
- self.message = message
- self.set_default_size(400, 400)
- self.set_default_response(gtk.RESPONSE_CLOSE)
- self.set_border_width(6)
- self.connect('response', self.on_response)
- view = gtk.TextView()
- view.get_buffer().set_text(self.message)
- self.vbox.pack_start(create_scrollable(view))
- self.show_all()
- def on_response(self, dialog, res):
- # ok gtk.RESPONSE_APPLY is ugly for gtk.STOCK_COPY, but who cares?
- # maybe adding it as a secondary button or such is better
- if res == gtk.RESPONSE_APPLY:
- clipboard = gtk.clipboard_get()
- clipboard.set_text(self.message)
- else:
- self.destroy()
- # BTS
- class Bug(object):
- """Encapsulate a bug report for the GTK+ UI"""
- def __init__(self, bug):
- self.id = bug.bug_num
- self.tag = u', '.join(bug.tags)
- self.package = bug.package
- self.status = bug.pending
- self.reporter = bug.originator
- self.date = bug.date
- self.severity = bug.severity
- self.version = u', '.join(bug.found_versions)
- self.filed_date = bug.date
- self.modified_date = bug.log_modified
- self.info = bug.subject
- def __iter__(self):
- yield self.id
- yield self.tag
- yield self.package
- yield self.info
- yield self.status
- 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 wrap_bug_body(self, msg, width=79, break_long_words=False):
- """Wrap every line in the message"""
- # resulting body text
- body = ''
- for line in msg.splitlines():
- # wrap long lines, it returns a list of "sub-lines"
- tmp = textwrap.wrap(line, width=width,
- break_long_words=break_long_words)
- # need to special-case this else a join() on the list generator
- # would remove all the '[]' so no empty lines in the report
- if tmp == []:
- body += '\n'
- else:
- # join the "sub-lines" and add a \n at the end(if there is
- # only one item in the list, else there wouldn't be a \n)
- body += '\n'.join(tmp) + '\n'
- return body
- def create_message(self, info):
- message = """%s
- %s
- %s""" % ('\n'.join(self.headers), self.wrap_bug_body(info), self.others)
- return message
- # BTS GUI
- class BugPage(gtk.EventBox, threading.Thread):
- def __init__(self, assistant, dialog, number, queryonly, bts, mirrors, http_proxy, timeout, archived):
- threading.Thread.__init__(self)
- gtk.EventBox.__init__(self)
- self.setDaemon(True)
- self.dialog = dialog
- self.assistant = assistant
- self.application = self.assistant.application
- self.number = number
- self.queryonly = queryonly
- self.bts = bts
- self.mirrors = mirrors
- self.http_proxy = http_proxy
- self.timeout = timeout
- self.archived = archived
- self.bug_status = None
- 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 = debbugs.get_report(int(self.number), self.timeout,
- 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.bug_status = info[0]
- self.application.run_once_in_main_thread(self.found, info)
- def drop_progressbar(self):
- child = self.get_child()
- if 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].subject
- bodies = info[1]
- vbox = gtk.VBox(spacing=12)
- vbox.set_border_width(12)
- label = gtk.Label('Description: ' + desc)
- label.set_line_wrap(True)
- label.set_justify(gtk.JUSTIFY_FILL)
- vbox.pack_start(label, expand=False)
- views = gtk.VBox()
- odd = False
- for body in bodies:
- view = gtk.TextView()
- view.set_editable(False)
- view.get_buffer().set_text(body)
- if odd:
- view.set_state(gtk.STATE_PRELIGHT)
- views.pack_start(view, False)
- odd = not odd
- scrolled = create_scrollable(views, True)
- vbox.pack_start(scrolled)
- bbox = gtk.HButtonBox()
- button = gtk.Button("Open in browser")
- button.connect('clicked', self.on_open_browser)
- bbox.pack_start(button)
- if not self.queryonly:
- 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(debbugs.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.bug_status)
- # Forward the assistant to the progress bar
- self.assistant.forward_page()
- # Though we're only a page, we are authorized to destroy our parent :)
- # This would be better handled connecting externally to self.reply_button
- self.dialog.destroy()
- class BugsDialog(gtk.Dialog):
- def __init__(self, assistant, queryonly):
- 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.queryonly = queryonly
- self.application = assistant.application
- self.notebook = gtk.Notebook()
- self.vbox.pack_start(self.notebook)
- self.connect('response', self.on_response)
- self.set_default_size(600, 600)
- def on_response(self, *args):
- self.destroy()
- def show_bug(self, number, *args):
- page = BugPage(self.assistant, self, number, self.queryonly, *args)
- self.notebook.append_page(page, gtk.Label(number))
- page.start()
- # Application
- class ReportbugApplication(threading.Thread):
- def __init__(self):
- threading.Thread.__init__(self)
- self.setDaemon(True)
- self.queue = Queue()
- self.next_value = None
- def run(self):
- gtk.gdk.threads_enter()
- gtk.main()
- gtk.gdk.threads_leave()
- 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))
- # Connection with reportbug
- # Syncronize "pipe" with reportbug
- class SyncReturn(RuntimeError):
- def __init__(self, result):
- RuntimeError.__init__(self, 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 = DEBIAN_LOGO
- WARNING_COLOR = gtk.gdk.color_parse("#fff8ae")
- 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
- def execute_operation(self, *args, **kwargs):
- self.switch_in()
- self.connect_signals()
- self.empty_ok = kwargs.pop('empty_ok', False)
- self.presubj = kwargs.pop('presubj', False)
- self.execute(*args, **kwargs)
- self.assistant.show()
- self.setup_focus()
- 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):
- Page.next_page_num += 1
- 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, gtk.gdk.pixbuf_new_from_file(self.side_image))
- self.assistant.set_next_page(self)
- self.set_page_title("Reportbug")
- # Setup keyboard focus in the page
- def setup_focus(self):
- self.widget.grab_focus()
- # Forward page when a widget is activated(e.g. GtkEntry) only if page is complete
- def activate_forward(self, *args):
- if self.assistant.get_page_complete(self.widget):
- self.assistant.forward_page()
- # The user forwarded the assistant to see the next page
- def switch_out(self):
- pass
- def is_valid(self, value):
- if self.empty_ok:
- return True
- else:
- return bool(value)
- def validate(self, *args, **kwargs):
- value = self.get_value()
- if self.is_valid(value):
- self.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(spacing=24)
- label = gtk.Label("""
- <b>Reportbug</b> is a tool designed to make the reporting of bugs in Debian and derived distributions relatively painless.
- This wizard will guide you through the bug reporting process step by step.
- <b>Note:</b> bug reports are publicly archived(including the email address of the submitter).""")
- label.set_use_markup(True)
- label.set_line_wrap(True)
- label.set_justify(gtk.JUSTIFY_FILL)
- vbox.pack_start(label, expand=False)
- link = gtk.LinkButton("http://alioth.debian.org/projects/reportbug",
- "Homepage of reportbug project")
- vbox.pack_start(link, expand=False)
- return vbox
- class GetStringPage(Page):
- def setup_focus(self):
- self.entry.grab_focus()
- def create_widget(self):
- vbox = gtk.VBox(spacing=12)
- self.label = gtk.Label()
- self.label.set_line_wrap(True)
- self.label.set_justify(gtk.JUSTIFY_FILL)
- self.label.set_selectable(True)
- self.label.set_property("can-focus", False)
- 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)
- self.entry.connect('activate', self.activate_forward)
- def get_value(self):
- return self.entry.get_text()
- def execute(self, prompt, options=None, force_prompt=False, default=''):
- # Hackish: remove the text needed for textual UIs...
- gobject.idle_add(self.label.set_text, prompt.replace('(enter Ctrl+c to exit reportbug without reporting a bug)', ''))
- self.entry.set_text(default)
- if options:
- options.sort()
- completion = gtk.EntryCompletion()
- model = gtk.ListStore(str)
- for option in options:
- model.append([option])
- completion.set_model(model)
- completion.set_inline_selection(True)
- completion.set_text_column(0)
- self.entry.set_completion(completion)
- else:
- self.completion = None
- self.validate()
- class GetPasswordPage(GetStringPage):
- def create_widget(self):
- widget = GetStringPage.create_widget(self)
- self.entry.set_visibility(False)
- return widget
- class GetMultilinePage(Page):
- def setup_focus(self):
- self.view.grab_focus()
- def create_widget(self):
- vbox = gtk.VBox(spacing=12)
- self.label = gtk.Label()
- self.label.set_line_wrap(True)
- self.label.set_justify(gtk.JUSTIFY_FILL)
- self.label.set_selectable(True)
- self.label.set_property("can-focus", False)
- vbox.pack_start(self.label, expand=False)
- self.view = gtk.TextView()
- self.buffer = view.get_buffer()
- scrolled = create_scrollable(self.view)
- vbox.pack_start(scrolled)
- return vbox
- def connect_signals(self):
- self.buffer.connect('changed', self.validate)
- def get_value(self):
- text = self.buffer.get_text(self.buffer.get_start_iter(), self.buffer.get_end_iter())
- lines = text.split('\n')
- # Remove the trailing empty line at the end
- if len(lines) > 0 and not lines[-1].strip():
- del lines[-1]
- return text.split('\n')
- def execute(self, prompt):
- self.empty_ok = True
- # The result must be iterable for reportbug even if it's empty and not modified
- gobject.idle_add(self.label.set_text, prompt)
- self.buffer.set_text("")
- self.buffer.emit('changed')
- class TreePage(Page):
- value_column = None
- def __init__(self, *args, **kwargs):
- Page.__init__(self, *args, **kwargs)
- self.selection = self.view.get_selection()
- def setup_focus(self):
- self.view.grab_focus()
- 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 GetListPage(TreePage):
- value_column = 0
- def create_widget(self):
- vbox = gtk.VBox(spacing=12)
- self.label = gtk.Label()
- self.label.set_line_wrap(True)
- self.label.set_justify(gtk.JUSTIFY_FILL)
- vbox.pack_start(self.label, expand=False)
- hbox = gtk.HBox(spacing=6)
- self.view = gtk.TreeView()
- self.view.set_rules_hint(True)
- self.view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
- scrolled = create_scrollable(self.view)
- hbox.pack_start(scrolled)
- bbox = gtk.VButtonBox()
- bbox.set_spacing(6)
- bbox.set_layout(gtk.BUTTONBOX_START)
- button = gtk.Button(stock=gtk.STOCK_ADD)
- button.connect('clicked', self.on_add)
- bbox.pack_start(button, expand=False)
- button = gtk.Button(stock=gtk.STOCK_REMOVE)
- button.connect('clicked', self.on_remove)
- bbox.pack_start(button, expand=False)
- hbox.pack_start(bbox, expand=False)
- vbox.pack_start(hbox)
- return vbox
- def get_value(self):
- values = []
- for row in self.model:
- values.append(row[self.value_column])
- return values
- def on_add(self, button):
- dialog = InputStringDialog("Add a new item to the list")
- dialog.show_all()
- dialog.connect('response', self.on_add_dialog_response)
- def on_add_dialog_response(self, dialog, res):
- if res == gtk.RESPONSE_ACCEPT:
- self.model.append([dialog.get_value()])
- dialog.destroy()
- def on_remove(self, button):
- model, paths = self.selection.get_selected_rows()
- # We need to transform them to iters, since paths change when removing rows
- iters = []
- for path in paths:
- iters.append(self.model.get_iter(path))
- for iter in iters:
- self.model.remove(iter)
- def execute(self, prompt):
- self.empty_ok = True
- gobject.idle_add(self.label.set_text, prompt)
- self.model = gtk.ListStore(str)
- self.model.connect('row-changed', self.validate)
- self.view.set_model(self.model)
- self.selection.set_mode(gtk.SELECTION_MULTIPLE)
- self.view.append_column(gtk.TreeViewColumn('Item', gtk.CellRendererText(), text=0))
- class WrapRendererText(gtk.CellRendererText):
- def do_render(self, window, widget, background_area, cell_area, expose_area, flags):
- self.set_property('wrap-width', cell_area.width)
- gtk.CellRendererText.do_render(self, window, widget, background_area, cell_area, expose_area, flags)
- gobject.type_register(WrapRendererText)
- class MenuPage(TreePage):
- value_column = 0
- def create_widget(self):
- vbox = gtk.VBox(spacing=12)
- self.label = gtk.Label()
- self.label.set_line_wrap(True)
- self.label.set_justify(gtk.JUSTIFY_FILL)
- vbox.pack_start(self.label, expand=False)
- self.view = gtk.TreeView()
- self.view.set_rules_hint(True)
- scrolled = create_scrollable(self.view)
- scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
- vbox.pack_start(scrolled)
- vbox.show_all()
- return vbox
- def connect_signals(self):
- TreePage.connect_signals(self)
- self.view.connect('row-activated', self.activate_forward)
- def execute(self, par, options, prompt, default=None, any_ok=False,
- order=None, extras=None, multiple=False):
- gobject.idle_add(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))
- rend = WrapRendererText()
- rend.set_property('wrap-mode', pango.WRAP_WORD)
- rend.set_property('wrap-width', 300)
- self.view.append_column(gtk.TreeViewColumn('Description', rend, text=1))
- default_iter = None
- # here below, 'text' is the value of the description of the item, but
- # writen all on a single-line, it will be wrapped by the list settings
- if isinstance(options, dict):
- if order:
- for option in order:
- if option in options:
- text = ' '.join(options[option].split())
- iter = self.model.append((highlight(option), text))
- if option == default:
- default_iter = iter
- for option, desc in options.iteritems():
- if not order or option not in order:
- text = ' '.join(desc.split())
- iter = self.model.append((highlight(option), text))
- if option == default:
- default_iter = iter
- else:
- for row in options:
- text = ' '.join(row[1].split())
- iter = self.model.append((highlight(row[0]), text))
- 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, timeout, mirrors=None, http_proxy="", queryonly=False, screen=None,
- archived='no', source=False, title=None,
- version=None, buglist=None, mbox_reader_cmd=None, latest_first=False):
- self.bts = bts
- self.mirrors = mirrors
- self.http_proxy = http_proxy
- self.timeout = timeout
- self.archived = archived
- self.queryonly = queryonly
- if queryonly:
- self.page_type = gtk.ASSISTANT_PAGE_CONFIRM
- sysinfo = debbugs.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' % (debbugs.SYSTEMS[bts]['name'], pkgname)
- else:
- progress_label = 'Querying %s bug tracking system for reports %s' % (debbugs.SYSTEMS[bts]['name'], ' '.join([str(x) for x in package]))
- self.application.run_once_in_main_thread(self.assistant.set_progress_label, progress_label)
- try:
- (count, sectitle, hierarchy) = debbugs.get_reports(
- package, timeout, bts, mirrors=mirrors, version=version,
- http_proxy=http_proxy, archived=archived, source=source)
- except:
- error_dialog("Unable to connect to %s BTS." % sysinfo['name'])
- raise NoBugs
- try:
- if not count:
- if hierarchy is 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)
- # XXX: this needs to be fixed in debianbts; Bugreport are
- # not sortable(on bug_num) - see #639458
- sorted(buglist, reverse=latest_first)
- report.append((category, map(Bug, buglist)))
- return(report, sectitle), {}
- except NoPackage:
- error_dialog('No record of this package found.')
- raise NoPackage
- raise SyncReturn(None)
- def setup_focus(self):
- self.entry.grab_focus()
- def create_widget(self):
- vbox = gtk.VBox(spacing=6)
- self.label = gtk.Label("List of bugs. Select a bug to retrieve and submit more information.")
- vbox.pack_start(self.label, expand=False, padding=6)
- hbox = gtk.HBox(spacing=6)
- label = gtk.Label("Filter:")
- hbox.pack_start(label, expand=False)
- self.entry = gtk.Entry()
- hbox.pack_start(self.entry)
- button = gtk.Button()
- button.set_image(gtk.image_new_from_stock(gtk.STOCK_CLEAR, gtk.ICON_SIZE_MENU))
- button.set_relief(gtk.RELIEF_NONE)
- button.connect('clicked', self.on_filter_clear)
- hbox.pack_start(button, expand=False)
- vbox.pack_start(hbox, expand=False)
- self.view = gtk.TreeView()
- self.view.set_rules_hint(True)
- scrolled = create_scrollable(self.view)
- self.columns = ['ID', 'Tag', 'Package', 'Description', 'Status', 'Submitter', 'Date', 'Severity', 'Version',
- 'Filed date', 'Modified date']
- for col in zip(self.columns, range(len(self.columns))):
- column = gtk.TreeViewColumn(col[0], gtk.CellRendererText(), text=col[1])
- column.set_reorderable(True)
- self.view.append_column(column)
- 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)
- self.entry.connect('changed', self.on_filter_changed)
- def on_filter_clear(self, button):
- self.entry.set_text("")
- def on_filter_changed(self, entry):
- self.model.filter_text = entry.get_text().lower()
- self.filter.refilter()
- 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, self.queryonly)
- for id in bug_ids:
- dialog.show_bug(id, self.bts, self.mirrors, self.http_proxy, self.timeout, 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 match_filter(self, iter):
- # Flatten the columns into a single string
- text = ""
- for col in range(len(self.columns)):
- value = self.model.get_value(iter, col)
- if value:
- text += self.model.get_value(iter, col) + " "
- text = text.lower()
- # Tokens shouldn't be adjacent by default
- for token in self.model.filter_text.split(' '):
- if token in text:
- return True
- return False
- def filter_visible_func(self, model, iter):
- matches = self.match_filter(iter)
- if not self.model.iter_parent(iter) and not matches:
- # If no children are visible, hide it
- it = model.iter_children(iter)
- while it:
- if self.match_filter(it):
- return True
- it = model.iter_next(it)
- return False
- return matches
- def execute(self, buglist, sectitle):
- gobject.idle_add(self.label.set_text, "%s. Double-click a bug to retrieve and submit more information." % sectitle)
- self.model = gtk.TreeStore(*([str] * len(self.columns)))
- for category in buglist:
- row = [None] * len(self.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)
- self.model.filter_text = ""
- self.filter = self.model.filter_new()
- self.filter.set_visible_func(self.filter_visible_func)
- self.view.set_model(self.filter)
- class ShowReportPage(Page):
- default_complete = True
- def create_widget(self):
- self.page = BugPage(self.assistant, None, None, None, None, None, None, None, None)
- return self.page
- def get_value(self):
- return None
- def is_valid(self, value):
- return True
- def sync_pre_operation(self, *args, **kwargs):
- if kwargs.get('queryonly'):
- self.page_type = gtk.ASSISTANT_PAGE_CONFIRM
- return args, kwargs
- def execute(self, number, system, mirrors, http_proxy, timeout, queryonly=False, title='', archived='no', mbox_reader_cmd=None):
- self.page.number = number
- self.page.bts = system
- self.page.mirrors = mirrors
- self.page.http_proxy = http_proxy
- self.page.timeout = timeout
- self.page.queryonly = queryonly
- self.page.archived = archived
- self.page.start()
- self.validate()
- 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):
- # 'use' args only if it's passed
- if args:
- message = message % args
- self.view.get_buffer().set_text(message)
- class LongMessagePage(Page):
- default_complete = True
- def create_widget(self):
- self.label = gtk.Label()
- self.label.set_line_wrap(True)
- self.label.set_justify(gtk.JUSTIFY_FILL)
- self.label.set_selectable(True)
- self.label.set_property("can-focus", False)
- eb = gtk.EventBox()
- eb.add(self.label)
- return eb
- def execute(self, message, *args):
- message = message % args
- # make it all on one line, it will be wrapped at display-time
- message = ' '.join(message.split())
- gobject.idle_add(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 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.view.modify_font(pango.FontDescription("Monospace"))
- self.view.set_wrap_mode(gtk.WRAP_WORD)
- if has_spell:
- gtkspellcheck.SpellChecker(self.view)
- 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, False)
- if not has_spell:
- box = gtk.EventBox()
- label = gtk.Label("Please install <b>python-gtkspellcheck</b> to enable spell checking")
- label.set_use_markup(True)
- label.set_line_wrap(True)
- label.set_selectable(True)
- label.set_property("can-focus", False)
- box.add(label)
- box.modify_bg(gtk.STATE_NORMAL, self.WARNING_COLOR)
- box.connect('button-press-event', lambda *args: box.destroy())
- vbox.pack_start(box, False)
- return vbox
- def switch_out(self):
- global report_message
- report_message = self.get_value()[0]
- f = file(self.filename, "w")
- f.write(report_message)
- 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.\nThe text will be wrapped to be max 79 chars long per line."
- self.handle_first_info()
- self.info_buffer.set_text(info)
- class SelectOptionsPage(Page):
- default_complete = False
- def create_widget(self):
- self.label = gtk.Label()
- self.label.set_line_wrap(True)
- self.label.set_justify(gtk.JUSTIFY_FILL)
- self.vbox = gtk.VBox(spacing=6)
- self.vbox.pack_start(self.label, expand=False, padding=6)
- self.default = None
- return self.vbox
- def on_clicked(self, button, menuopt):
- self.application.set_next_value(menuopt)
- self.assistant.forward_page()
- def on_display_clicked(self, button):
- global report_message
- ReportViewerDialog(report_message)
- def setup_focus(self):
- if self.default:
- self.default.set_flags(gtk.CAN_DEFAULT | gtk.HAS_DEFAULT)
- self.default.grab_default()
- self.default.grab_focus()
- def execute(self, prompt, menuopts, options):
- # remove text UI indication
- prompt = prompt.replace('(e to edit)', '')
- gobject.idle_add(self.label.set_text, prompt)
- buttons = []
- for menuopt in menuopts:
- desc = options[menuopt.lower()]
- # do we really need to launch an external editor?
- if 'Change editor' in desc:
- continue
- # this will be handled using the text view below
- if 'Pipe the message through the pager' in desc:
- continue
- # stdout is a textview for us
- if 'Print message to stdout' in desc:
- button = gtk.Button("Display message in a text view")
- button.connect('clicked', self.on_display_clicked)
- buttons.append(button)
- else:
- button = gtk.Button()
- label = gtk.Label(options[menuopt.lower()])
- button.add(label)
- button.connect('clicked', self.on_clicked, menuopt.lower())
- if menuopt.isupper():
- label.set_markup("<b>%s</b>" % label.get_text())
- self.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)
- self.vbox.show_all()
- class SystemPage(Page):
- default_complete = False
- def create_widget(self):
- hbox = gtk.HBox()
- self.terminal = vte.Terminal()
- self.terminal.set_cursor_blinks(True)
- self.terminal.set_emulation("xterm")
- self.terminal.connect('child-exited', self.on_child_exited)
- hbox.pack_start(self.terminal)
- scrollbar = gtk.VScrollbar()
- scrollbar.set_adjustment(self.terminal.get_adjustment())
- hbox.pack_start(scrollbar)
- return hbox
- def on_child_exited(self, terminal):
- self.application.set_next_value(None)
- self.assistant.forward_page()
- def execute(self, cmdline):
- self.terminal.fork_command('/bin/bash', ['/bin/bash', '-c', cmdline])
- 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.label.set_line_wrap(True)
- self.label.set_justify(gtk.JUSTIFY_FILL)
- 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):
- gobject.idle_add(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.application = application
- self.set_title('Reportbug')
- self.hack_buttons()
- self.showing_page = None
- self.requested_page = None
- self.progress_page = None
- self.set_default_size(600, 400)
- self.set_forward_page_func(self.forward)
- self.connect_signals()
- self.setup_pages()
- def _hack_buttons(self, widget):
- # This is a real hack for two reasons:
- # 1. There's no other way to access action area but inspecting the assistant and searching for the back button
- # 2. Hide back button on show, because it can be shown-hidden by the assistant depending on the page
- if isinstance(widget, gtk.Button):
- if widget.get_label() == 'gtk-go-back':
- widget.connect('show', self.on_back_show)
- return
- if widget.get_label() == 'gtk-apply':
- widget.connect('show', self.on_back_show)
- return
- if widget.get_label() == 'gtk-cancel':
- image = gtk.image_new_from_stock(gtk.STOCK_QUIT,
- gtk.ICON_SIZE_BUTTON)
- widget.set_label("_Quit")
- widget.set_image(image)
- return
- if widget.get_label() == 'gtk-go-forward':
- image = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_BUTTON)
- widget.set_label("_Continue")
- widget.set_image(image)
- return
- if isinstance(widget, gtk.Container):
- widget.forall(self._hack_buttons)
- def hack_buttons(self):
- self._hack_buttons(self)
- def connect_signals(self):
- self.connect('cancel', self.confirm_exit)
- self.connect('prepare', self.on_prepare)
- self.connect('delete-event', self.close)
- self.connect('apply', self.close)
- def on_back_show(self, widget):
- widget.hide()
- 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()
- gobject.idle_add(self.showing_page.setup_focus)
- def close(self, *args):
- sys.exit(0)
- def confirm_exit(self, *args):
- dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO,
- "Are you sure you want to quit Reportbug?")
- response = dialog.run()
- dialog.destroy()
- if response == gtk.RESPONSE_YES:
- 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.showing_page.switch_in()
- self.progress_page = ProgressPage(self)
- self.progress_page.switch_in()
- Page.next_page_num = 1
- # 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)
- if default:
- self.set_default_response(gtk.RESPONSE_YES)
- else:
- self.set_default_response(gtk.RESPONSE_NO)
- 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, *args):
- self.set_markup(msg % args)
- 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()
- def log_message(*args, **kwargs):
- return assistant.set_progress_label(*args, **kwargs)
- def select_multiple(*args, **kwargs):
- kwargs['multiple'] = True
- kwargs['empty_ok'] = True
- return menu(*args, **kwargs)
- def get_multiline(prompt, *args, **kwargs):
- if 'ENTER' in prompt:
- # This is a list, let's handle it the best way
- return get_list(prompt, *args, **kwargs)
- else:
- return get_multiline(prompt, *args, **kwargs)
- pages = {'get_string': GetStringPage,
- 'get_password': GetPasswordPage,
- 'menu': MenuPage,
- 'handle_bts_query': HandleBTSQueryPage,
- 'show_report': ShowReportPage,
- 'long_message': LongMessagePage,
- 'display_report': DisplayReportPage,
- 'final_message': FinalMessagePage,
- 'spawn_editor': EditorPage,
- 'select_options': SelectOptionsPage,
- 'get_list': GetListPage,
- 'system': SystemPage,
- }
- dialogs = {'yes_no': YesNoDialog,
- 'get_filename': GetFilenameDialog,
- 'display_failure': DisplayFailureDialog,
- }
- 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)
- def initialize():
- global application, assistant, vte
- try:
- vte = __import__("vte")
- except ImportError:
- message = """Please install the %s package to use the GTK+(known as 'gtk2' in reportbug) interface.
- Falling back to 'text' interface."""
- dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
- gtk.MESSAGE_INFO, gtk.BUTTONS_CLOSE, None)
- dialog.set_markup(message % "<b>python-vte</b>")
- dialog.run()
- dialog.destroy()
- while gtk.events_pending():
- gtk.main_iteration()
- if not sys.stdout.isatty():
- os.execlp('x-terminal-emulator', 'x-terminal-emulator', '-e', 'reportbug -u text')
- return False
- # Exception hook
- oldhook = sys.excepthook
- sys.excepthook = ExceptionDialog.create_excepthook(oldhook)
- # GTK settings
- gtk.window_set_default_icon_from_file(DEBIAN_LOGO)
- gtk.link_button_set_uri_hook(lambda button, uri: launch_browser(uri))
- application = ReportbugApplication()
- assistant = ReportbugAssistant(application)
- # Forwarders
- forward_operations(assistant, pages)
- forward_operations(application, dialogs)
- application.start()
- return True
- def can_input():
- return True
|