# -*- coding: utf-8 -*-
import sys
import pwd
import getopt
from abrt_utils import _, init_logging, log, log1, log2
import gobject
gobject.set_prgname(_("Automatic Bug Reporting Tool"))
import pygtk
pygtk.require("2.0")
try:
import gtk
except RuntimeError,e:
# rhbz#552039
print e
sys.exit()
import gtk.glade
from ConfBackend import getCurrentConfBackend, ConfBackendInitError
import CCDBusBackend
from CC_gui_functions import *
from CCDumpList import getDumpList
from CCDump import * # FILENAME_xxx, CD_xxx
from CReporterAssistant import ReporterAssistant
from PluginsSettingsDialog import PluginsSettingsDialog
from SettingsDialog import SettingsDialog
from PluginList import getPluginInfoList
import ABRTExceptions
class MainWindow():
ccdaemon = None
def __init__(self, daemon):
self.theme = gtk.icon_theme_get_default()
self.updates = ""
self.ccdaemon = daemon
#Set the Glade file
self.gladefile = "%s/ccgui.glade" % sys.path[0]
self.wTree = gtk.glade.XML(self.gladefile)
#Get the Main Window, and connect the "destroy" event
self.window = self.wTree.get_widget("main_window")
if self.window:
self.window.set_default_size(600, 700)
self.window.connect("delete_event", self.delete_event_cb)
self.window.connect("destroy", self.destroy)
self.window.connect("focus-in-event", self.focus_in_cb)
self.wTree.get_widget("vp_details").modify_bg(gtk.STATE_NORMAL,gtk.gdk.color_parse("#FFFFFF"))
#init the dumps treeview
self.dlist = self.wTree.get_widget("tvDumps")
#rows of items with:
STATUS_COL = 0
APP_NAME_COL = 1
TIME_STR_COL = 2
HOSTNAME_COL = 3
UNIX_TIME_COL = 4
DUMP_OBJECT_COL = 5
#is_reported, application_name, hostname, date, time_in_sec ?object?
self.dumpsListStore = gtk.ListStore(str, str, str, str, int, object)
self.dlist.set_model(self.dumpsListStore)
# add pixbuff separatelly
icon_column = gtk.TreeViewColumn(_("Reported"))
icon_column.cell = gtk.CellRendererPixbuf()
#icon_column.cell.set_property('cell-background', "#C9C9C9")
n = self.dlist.append_column(icon_column)
icon_column.pack_start(icon_column.cell, True)
icon_column.set_attributes(icon_column.cell, stock_id=(n-1))# cell_background_set=6)
# ===============================================
columns = []
columns.append(gtk.TreeViewColumn(_("Application")))
columns[-1].set_sort_column_id(APP_NAME_COL)
columns.append(gtk.TreeViewColumn(_("Hostname")))
columns[-1].set_sort_column_id(HOSTNAME_COL)
columns.append(gtk.TreeViewColumn(_("Latest Crash")))
columns[-1].set_sort_column_id(UNIX_TIME_COL)
# add cells to colums and bind cells to the liststore values
for column in columns:
n = self.dlist.append_column(column)
column.cell = gtk.CellRendererText()
column.pack_start(column.cell, False)
column.set_attributes(column.cell, text=(n-1))
column.set_resizable(True)
#connect signals
self.dlist.connect("cursor-changed", self.on_tvDumps_cursor_changed)
self.dlist.connect("row-activated", self.on_dumpRowActivated)
self.dlist.connect("button-press-event", self.on_popupActivate)
self.wTree.get_widget("bDelete").connect("clicked", self.on_bDelete_clicked, self.dlist)
self.wTree.get_widget("bReport").connect("clicked", self.on_bReport_clicked)
self.wTree.get_widget("b_close").connect("clicked", self.on_bQuit_clicked)
self.wTree.get_widget("b_copy").connect("clicked", self.on_b_copy_clicked)
self.wTree.get_widget("b_help").connect("clicked", self.on_miOnlineHelp_clicked)
self.wTree.get_widget("miQuit").connect("activate", self.on_bQuit_clicked)
self.wTree.get_widget("miAbout").connect("activate", self.on_miAbout_clicked)
self.wTree.get_widget("miOnlineHelp").connect("activate", self.on_miOnlineHelp_clicked)
self.wTree.get_widget("miPlugins").connect("activate", self.on_miPreferences_clicked)
self.wTree.get_widget("miPreferences").connect("activate", self.on_miSettings_clicked)
self.wTree.get_widget("miReport").connect("activate", self.on_bReport_clicked)
self.wTree.get_widget("miDelete").connect("activate", self.on_bDelete_clicked, self.dlist)
# connect handlers for daemon signals
self.ccdaemon.connect("crash", self.on_data_changed_cb, None)
self.ccdaemon.connect("abrt-error", self.error_cb)
#self.ccdaemon.connect("update", self.update_cb)
# for now, just treat them the same (w/o this, we don't even see daemon warnings in logs!):
#self.ccdaemon.connect("warning", self.update_cb)
self.ccdaemon.connect("show_gui", self.show_cb)
self.ccdaemon.connect("daemon-state-changed", self.on_daemon_state_changed_cb)
self.ccdaemon.connect("report-done", self.on_report_done_cb)
self.pluginlist = None
def on_report_done_cb(self, daemon, result):
self.hydrate()
def on_daemon_state_changed_cb(self, widget, state):
if state == "up":
self.hydrate() # refresh crash list
#self.window.set_sensitive(True)
# abrtd might just die on timeout, it's not fatal
#elif state == "down":
# self.window.set_sensitive(False)
def on_popupActivate(self, widget, event):
menu = self.wTree.get_widget("popup_menu")
# 3 == right mouse button
if event.button == 3:
menu.popup(None, None, None, event.button, event.time)
def on_miAbout_clicked(self, widget):
dialog = self.wTree.get_widget("about")
result = dialog.run()
dialog.hide()
def on_miOnlineHelp_clicked(self, widget):
# opens default browser and shows ABRT chapter from deployment guide
gtk.show_uri(None, "http://docs.fedoraproject.org/en-US/Fedora/14/html/Deployment_Guide/ch-abrt.html", gtk.gdk.CURRENT_TIME)
def on_miPreferences_clicked(self, widget):
dialog = PluginsSettingsDialog(self.window,self.ccdaemon)
dialog.hydrate()
dialog.show()
def on_miSettings_clicked(self, widget):
dialog = SettingsDialog(self.window, self.ccdaemon)
try:
dialog.hydrate()
except Exception, ex:
gui_error_message(_("Cannot show the settings dialog.\n%s" % ex))
return
dialog.show()
def error_cb(self, daemon, message=None):
gui_error_message(_("Unable to finish the current task!\n%s" % message), parent_dialog=self.window)
def update_cb(self, daemon, message):
self.updates += message
if self.updates[-1] != '\n':
self.updates += '\n'
message = message.replace('\n',' ')
self.wTree.get_widget("lStatus").set_text(message)
buff = gtk.TextBuffer()
buff.set_text(self.updates)
end = buff.get_insert()
tvUpdates = self.wTree.get_widget("tvUpdates")
tvUpdates.set_buffer(buff)
tvUpdates.scroll_mark_onscreen(end)
def get_username_from_uid(self, uid):
# if uid == None or "" return it back
if not uid:
return uid
user = "N/A"
if uid != "-1": # compat: only abrt <= 1.0.9 used UID = -1
try:
user = pwd.getpwuid(int(uid))[0]
except Exception, ex:
user = "UID: %s" % uid
return user
def hydrate(self):
n = None
self.dumpsListStore.clear()
try:
dumplist = getDumpList(self.ccdaemon, refresh=True)
except Exception, ex:
# there is something wrong with the daemon if we cant get the dumplist
gui_error_message(_("Error while loading the dumplist.\n%s" % ex))
# so we shouldn't continue..
sys.exit()
for entry in dumplist[::-1]:
n = self.dumpsListStore.append([["gtk-no","gtk-yes"][entry.getMessage() != ""],
entry.getExecutable(),
entry.get_hostname(),
entry.getTime("%c"),
entry.getTime(),
entry])
# activate the first row if any..
if n:
# we can use (0,) as path for the first row, but what if API changes?
self.dlist.set_cursor(self.dumpsListStore.get_path(self.dumpsListStore.get_iter_first()))
def filter_dumps(self, model, miter, data):
# for later..
return True
def dumplist_get_selected(self):
selection = self.dlist.get_selection()
if selection:
# returns (dumpsListStore, path) tuple
dumpsListStore, path = selection.get_selected_rows()
return dumpsListStore, path
else:
return None, None
def on_tvDumps_cursor_changed(self, treeview):
dumpsListStore, path = self.dumplist_get_selected()
if not path:
self.wTree.get_widget("bDelete").set_sensitive(False)
self.wTree.get_widget("bReport").set_sensitive(False)
self.wTree.get_widget("b_copy").set_sensitive(False)
# create an empty dump to fill the labels with empty strings
self.wTree.get_widget("sw_details").hide()
return
else:
self.wTree.get_widget("sw_details").show()
self.wTree.get_widget("bDelete").set_sensitive(True)
self.wTree.get_widget("bReport").set_sensitive(True)
self.wTree.get_widget("b_copy").set_sensitive(True)
# this should work until we keep the row object in the last position
dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]),
dumpsListStore.get_n_columns()-1)
try:
icon = get_icon_for_package(self.theme, dump.getPackageName())
except:
icon = None
i_package_icon = self.wTree.get_widget("i_package_icon")
if icon:
i_package_icon.set_from_pixbuf(icon)
else:
i_package_icon.set_from_icon_name("application-x-executable", gtk.ICON_SIZE_DIALOG)
l_heading = self.wTree.get_widget("l_detail_heading")
l_heading.set_markup(_("%s Crash\n%s") % (dump.getPackageName().title(),dump.getPackage()))
# process the labels in sw_details
# hide the fields that are not filled by daemon - e.g. comments
# and how to reproduce
for field in dump.not_required_fields:
self.wTree.get_widget("l_%s" % field.lower()).hide()
self.wTree.get_widget("l_%s_heading" % field.lower()).hide()
# fill the details
# read attributes from CCDump object and if a corresponding label is
# found, then the label text is set to the attribute's value
# field names in glade file:
# heading label: l__heading
# text label: l_
for att in dump.__dict__:
label = self.wTree.get_widget("l_%s" % str(att).lower())
if label:
label.show()
if att in dump.not_required_fields:
try:
lbl_heading = self.wTree.get_widget("l_%s_heading" % str(att).lower())
lbl_heading.show()
except:
# we don't care if we fail to show the heading, it will
# break the gui a little, but it's better then exit
log2("failed to show the heading for >%s< : %s" % (att,e))
pass
if dump.__dict__[att] != None:
label.set_text(dump.__dict__[att])
else:
label.set_text("")
self.wTree.get_widget("l_date").set_text(dump.getTime("%c"))
self.wTree.get_widget("l_user").set_text(self.get_username_from_uid(dump.getUID()))
#move this to Dump class
hb_reports = self.wTree.get_widget("hb_reports")
lReported = self.wTree.get_widget("l_message")
if dump.getMessage() != "":
hb_reports.show()
report_label_raw = ""
report_label = ""
# plugin message follows, but at least in case of kerneloops,
# it is not informative (no URL to the report)
for message in dump.getMessage().split(';'):
if message:
report_message = tag_urls_in_text(message)
report_label += "%s\n" % report_message
report_label_raw += "%s\n" % message
log2("setting markup '%s'", report_label)
# Sometimes (!) set_markup() fails with
# "GtkWarning: Failed to set text from markup due to error parsing
# markup: Unknown tag 'a'" If it does, then set_text()
# in "fill the details" above acts as a fallback
lReported.set_markup(report_label)
else:
hb_reports.hide()
def mark_last_selected_row(self, dump_list_store, path, iter, last_selected_DumpDir):
# Get dump object from list (in our list it's in last col)
dump = dump_list_store.get_value(iter, dump_list_store.get_n_columns()-1)
if dump.getDumpDir() == last_selected_DumpDir:
self.dlist.set_cursor(dump_list_store.get_path(iter)[0])
return True # done, stop iteration
return False
def on_bDelete_clicked(self, button, treeview):
dumpsListStore, path = self.dumplist_get_selected()
if not path:
return
# this should work until we keep the dump object in the last position
dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1)
next_iter = dumpsListStore.iter_next(dumpsListStore.get_iter(path[0]))
last_dump = None
if next_iter:
last_dump = dumpsListStore.get_value(next_iter, dumpsListStore.get_n_columns()-1)
try:
self.ccdaemon.DeleteDebugDump(dump.getDumpDir())
self.hydrate()
if last_dump:
# we deleted the selected line, so we want to select the next one
dumpsListStore.foreach(self.mark_last_selected_row, last_dump.getDumpDir())
treeview.emit("cursor-changed")
except Exception, ex:
print ex
def dumplist_get_selected_values(self):
dumpsListStore, path = self.dumplist_get_selected()
if path and dumpsListStore:
return dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1)
return None
def on_b_copy_clicked(self, button):
clipboard = gtk.clipboard_get()
dump = self.dumplist_get_selected_values()
if not dump:
gui_info_dialog(_("You have to select a crash to copy."), parent=self.window)
return
# dictionaries are not sorted, so we need this as a workaround
dumpinfo = [("Package:", dump.package),
("Latest Crash:", dump.date),
("Command:", dump.cmdline),
("Reason:", dump.reason),
("Comment:", dump.comment),
("Bug Reports:", dump.message),
]
dumpinfo_text = ""
for line in dumpinfo:
dumpinfo_text += ("%-12s\t%s" % (line[0], line[1])).replace('\n','\n\t\t')
dumpinfo_text += '\n'
clipboard.set_text(dumpinfo_text)
def destroy(self, widget, data=None):
gtk.main_quit()
def on_data_changed_cb(self, *_args):
# FIXME mark the new entry somehow....
# remember the selected row
last_dump = None
dumpsListStore, path = self.dumplist_get_selected()
if path and dumpsListStore:
last_dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1)
self.hydrate()
if last_dump:
# re-select the line that was selected before a new crash happened
dumpsListStore.foreach(self.mark_last_selected_row, last_dump.getDumpDir())
def on_bReport_clicked(self, button):
dumpsListStore, path = self.dumplist_get_selected()
self.on_dumpRowActivated(self.dlist, None, path, None)
def on_dumpRowActivated(self, treeview, it, path, user_data=None):
dumpsListStore, path = self.dumplist_get_selected()
if not path:
return
dump = dumpsListStore.get_value(dumpsListStore.get_iter(path[0]), dumpsListStore.get_n_columns()-1)
# Do we want to let user decide which UI they want to use?
#rs = ReporterSelector(dump, self.ccdaemon, parent=self.window)
#rs.show()
assistant = ReporterAssistant(dump, self.ccdaemon, parent=self.window)
assistant.hydrate()
def delete_event_cb(self, widget, event, data=None):
gtk.main_quit()
def focus_in_cb(self, widget, event, data=None):
self.window.set_urgency_hint(False)
def on_bQuit_clicked(self, widget):
try:
gtk.main_quit()
except: # prevent "RuntimeError: called outside of a mainloop"
sys.exit()
def show(self):
self.window.show_all()
def show_cb(self, daemon):
if self.window:
if self.window.is_active():
return
self.window.set_urgency_hint(True)
self.window.present()
if __name__ == "__main__":
verbose = 0
crashid = None
try:
opts, args = getopt.getopt(sys.argv[1:], "vh", ["help", "report="])
except getopt.GetoptError, err:
print str(err) # prints something like "option -a not recognized"
sys.exit(2)
for opt, arg in opts:
if opt == "-v":
verbose += 1
elif opt == "--report":
crashid=arg
elif opt in ("-h", "--help"):
print _("Usage: abrt-gui [OPTIONS]"
"\n\t-v[vv]\t\t\tVerbose"
"\n\t--report=CRASH_ID\tDirectly report crash with CRASH_ID"
)
sys.exit()
init_logging("abrt-gui", verbose)
log1("log level:%d", verbose)
try:
daemon = CCDBusBackend.DBusManager()
except ABRTExceptions.IsRunning:
# another instance is running, so exit quietly
sys.exit()
except Exception, ex:
# show error message if connection fails
gui_error_message("%s" % ex)
sys.exit()
if crashid:
dumplist = getDumpList(daemon)
crashdump = dumplist.getDumpByCrashID(crashid)
if not crashdump:
gui_error_message(_("No such crash in the database, probably wrong crashid."
"\ncrashid=%s" % crashid))
sys.exit()
assistant = ReporterAssistant(crashdump, daemon, parent=None)
assistant.hydrate()
# Do we want to let the users to decide which UI to use?
# rs = ReporterSelector(crashdump, daemon, parent=None)
# rs.show()
else:
cc = MainWindow(daemon)
cc.hydrate()
cc.show()
gtk.main()