From 4a5c1ae905ceb0599bdea7d113cfc659e9740832 Mon Sep 17 00:00:00 2001 From: Martin Sivak Date: Thu, 24 Apr 2008 14:43:04 +0200 Subject: Prepare gui for packaging and update the spec file --- frontend/frontend_gtk.py | 606 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 606 insertions(+) create mode 100644 frontend/frontend_gtk.py (limited to 'frontend/frontend_gtk.py') diff --git a/frontend/frontend_gtk.py b/frontend/frontend_gtk.py new file mode 100644 index 0000000..2a2e7f4 --- /dev/null +++ b/frontend/frontend_gtk.py @@ -0,0 +1,606 @@ +# File name: main.py +# Date: 2008/04/21 +# Author: Martin Sivak +# +# Copyright (C) Red Hat 2008 +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# in a file called COPYING along with this program; if not, write to +# the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA +# 02139, USA. + +import gtk +import gtk.glade +import gobject #we need gobject.idle_add +import copy +import logging +from pyfirstaidkit import reporting +import pprint +import os.path +import thread + +class CallbacksMainWindow(object): + def __init__(self, dialog, cfg, tasker, glade, data): + self._dialog = dialog + self._tasker = tasker + self._glade = glade + self._cfg = cfg + self._data = data + self._running_lock = thread.allocate_lock() + + def execute(self): + if not self._running_lock.acquire(0): + return + + def _o(pages, stopbutton): + """Always return False -> remove from the idle queue after first execution""" + for i in range(pages.get_n_pages()): + pages.get_nth_page(i).set_sensitive(True) + stopbutton.set_sensitive(False) + return False + + def worker(*args): + self._cfg.lock() + self._tasker.run() + self._cfg.unlock() + gobject.idle_add(_o, *args) + self._running_lock.release() + + self._data.pages.set_current_page(-1) + for i in range(self._data.pages.get_n_pages())[:-1]: + self._data.pages.get_nth_page(i).set_sensitive(False) + self.on_b_ResetResults_activate(None) + + stopbutton = self._glade.get_widget("b_StopResults") + stopbutton.set_sensitive(True) + thread.start_new_thread(worker, (self._data.pages, stopbutton)) + + #menu callbacks + def on_mainmenu_open_activate(self, widget, *args): + print "on_mainmenu_open_activate" + d = gtk.FileChooserDialog(title="Load the configuration file", parent=self._dialog, action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + print d.run() + d.destroy() + return True + + def on_mainmenu_save_activate(self, widget, *args): + print "on_mainmenu_save_activate" + d = gtk.FileChooserDialog(title="Save the configuration file", parent=self._dialog, action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + ret=d.run() + + if ret==gtk.RESPONSE_ACCEPT: + try: + filename = d.get_filename() + fd = open(filename, "w") + self._cfg.write(fd) + except IOError, e: + pass + + d.destroy() + return True + + def on_quit_activate(self, widget, *args): + print "on_quit_activate" + if True: #XXX destroy right now, but warn, in final code we have to wait until plugin finishes + print "!!! You should wait until the running plugin finishes!!" + self._dialog.destroy() + return True + + def on_destroy(self, widget, *args): + print "on_destroy" + self._tasker.end() + del self._tasker + del self._cfg + del self._data.result_list_iter + gtk.main_quit() + + def on_mainmenu_about_activate(self, widget, *args): + print "on_mainmenu_about_activate" + return True + + #simple mode callbacks + def on_b_StartSimple_activate(self, widget, *args): + print "on_b_StartSimple_activate" + + flags = set(self._cfg.operation._list("flags")) + + #check fix + if self._glade.get_widget("check_Simple_Fix").get_active(): + self._cfg.operation.mode = "auto-flow" + self._cfg.operation.flow = "fix" + else: + self._cfg.operation.mode = "auto-flow" + self._cfg.operation.flow = "diagnose" + + #check interactive + if self._glade.get_widget("check_Simple_Interactive").get_active(): + self._cfg.operation.interactive = "True" + else: + self._cfg.operation.interactive = "False" + + #check verbose + if self._glade.get_widget("check_Simple_Verbose").get_active(): + self._cfg.operation.verbose = "True" + else: + self._cfg.operation.verbose = "False" + + #check experimental + if self._glade.get_widget("check_Simple_Experimental").get_active(): + flags.add("experimental") + else: + try: + flags.remove("experimental") + except KeyError, e: + pass + + self._cfg.operation.flags = " ".join(map(lambda x: x.encode("string-escape"), flags)) + self.execute() + return True + + #advanced mode callbacks + def on_b_StartAdvanced_activate(self, widget, *args): + print "on_b_StartAdvanced_activate" + + flags = set(self._cfg.operation._list("flags")) + + #set the auto-flow + self._cfg.operation.mode = "auto-flow" + + idx = self._data.flow_list.get_active_iter() + if idx is None: + return True + self._cfg.operation.flow = self._data.flow_list_store.get_value(idx,0) + + #check verbose + if self._glade.get_widget("check_Advanced_Verbose").get_active(): + self._cfg.operation.verbose = "True" + else: + self._cfg.operation.verbose = "False" + + #check experimental + if self._glade.get_widget("check_Advanced_Experimental").get_active(): + flags.add("experimental") + else: + try: + flags.remove("experimental") + except KeyError, e: + pass + + #check interactive + if self._glade.get_widget("check_Advanced_Interactive").get_active(): + self._cfg.operation.interactive = "True" + else: + self._cfg.operation.interactive = "False" + + #check dependency + if self._glade.get_widget("check_Advanced_Dependency").get_active(): + self._cfg.operation.dependencies = "True" + else: + self._cfg.operation.dependencies = "False" + + self._cfg.operation.flags = " ".join(map(lambda x: x.encode("string-escape"), flags)) + + self.execute() + return True + + #expert mode callbacks + def on_b_FlagsExpert_activate(self, widget, *args): + print "on_b_FlagsExpert_activate" + FlagList(self._cfg, self._tasker.flags(), dir = os.path.dirname(self._glade.relative_file("."))) + return True + + def on_b_InfoExpert_activate(self, widget, *args): + print "on_b_InfoExpert_activate" + + path,focus = self._data.plugin_list.get_cursor() + if path is None: + return True + + iter = self._data.plugin_list_store.get_iter(path) + pluginname = self._data.plugin_list_store.get_value(iter, 4) + print "Selected: ", pluginname + + PluginInfo(self._tasker.pluginsystem().getplugin(pluginname), dir = os.path.dirname(self._glade.relative_file("."))) + + return True + + def on_b_StartExpert_activate(self, widget, *args): + print "on_b_StartExpert_activate" + + #check verbose + if self._glade.get_widget("check_Expert_Verbose").get_active(): + self._cfg.operation.verbose = "True" + else: + self._cfg.operation.verbose = "False" + + #check interactive + if self._glade.get_widget("check_Expert_Interactive").get_active(): + self._cfg.operation.interactive = "True" + else: + self._cfg.operation.interactive = "False" + + #check dependency + if self._glade.get_widget("check_Expert_Dependency").get_active(): + self._cfg.operation.dependencies = "True" + else: + self._cfg.operation.dependencies = "False" + + #get the plugin & flow list + plugins = [] + flows = [] + + for pname,iter in self._data.plugin_iter.iteritems(): + childiter = self._data.plugin_list_store.iter_children(iter) + while childiter is not None: + if self._data.plugin_list_store.get_value(childiter, 0): #checkbox is checked + plugins.append(pname) + flows.append(self._data.plugin_list_store.get_value(childiter, 1)) + childiter = self._data.plugin_list_store.iter_next(childiter) + + plugins = map(lambda x: x.encode("string-escape"), plugins) + flows = map(lambda x: x.encode("string-escape"), flows) + + #set the flow mode + self._cfg.operation.mode = "flow" + self._cfg.operation.flow = " ".join(flows) + self._cfg.operation.plugin = " ".join(plugins) + + self.execute() + return True + + #results callbacks + def on_b_ResetResults_activate(self, widget, *args): + print "on_b_ResetResults_activate" + self._data.result_list_store.clear() + del self._data.result_list_iter + self._data.result_list_iter = dict() + return True + + def on_b_StopResults_activate(self, widget, *args): + print "on_b_StopResults_activate" + self._tasker.interrupt() + return True + +class CallbacksFlagList(object): + def __init__(self, dialog, cfg, flags): + self._dialog = dialog + self._flags = flags + self._cfg = cfg + + def on_b_OK_activate(self, widget, *args): + print "on_b_OK_activate" + + f = set() + for k,w in self._flags.iteritems(): + if w.get_active(): + f.add(k) + + if len(f)==0: + self._cfg.operation.flags = "" + else: + self._cfg.operation.flags = " ".join(map(lambda x: x.encode("string-escape"), f)) + + self._dialog.destroy() + return True + + def on_b_Cancel_activate(self, widget, *args): + print "on_b_Cancel_activate" + self._dialog.destroy() + return True + +class MainWindow(object): + def __init__(self, cfg, tasker, importance = logging.INFO, dir=""): + self._importance = importance + self._cfg = cfg + self._glade = gtk.glade.XML(os.path.join(dir, "firstaidkit.glade"), "MainWindow") + self._window = self._glade.get_widget("MainWindow") + self._cb = CallbacksMainWindow(self._window, cfg, tasker, self._glade, self) + self._glade.signal_autoconnect(self._cb) + self._window.connect("destroy", self._cb.on_destroy) + + self.pages = self._glade.get_widget("pages") + self.status_text = self._glade.get_widget("status_text") + self.status_progress = self._glade.get_widget("status_progress") + + self.plugin_list_store = gtk.TreeStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) + self.plugin_list = self._glade.get_widget("tree_Expert") + self.plugin_list.set_model(self.plugin_list_store) + + self.plugin_rend_text = gtk.CellRendererText() + self.plugin_rend_toggle = gtk.CellRendererToggle() + self.plugin_rend_toggle.set_radio(False) + self.plugin_rend_toggle.set_property("activatable", False) + + def plugin_rend_text_func(column, cell_renderer, tree_model, iter, user_data): + if tree_model.iter_depth(iter)==0: + cell_renderer.set_property("cell-background-set", True) + cell_renderer.set_property("cell-background-gdk", gtk.gdk.Color(red=50000, green=50000, blue=50000)) + cell_renderer.set_property("markup", "" + tree_model.get_value(iter, user_data) + "") + else: + cell_renderer.set_property("cell-background-set", False) + cell_renderer.set_property("text", tree_model.get_value(iter, user_data)) + return + + def plugin_rend_toggle_func(column, cell_renderer, tree_model, iter, user_data = None): + if tree_model.iter_depth(iter)==0: + cell_renderer.set_property("activatable", False) + cell_renderer.set_property("active", False) + cell_renderer.set_property("visible", False) + cell_renderer.set_property("cell-background-set", True) + cell_renderer.set_property("cell-background-gdk", gtk.gdk.Color(red=40000, green=40000, blue=40000)) + else: + cell_renderer.set_property("activatable", True) + cell_renderer.set_property("active", tree_model.get_value(iter,0)) + cell_renderer.set_property("visible", True) + cell_renderer.set_property("cell-background-set", False) + return + + def plugin_rend_toggle_cb(cell, path, data): + model, col = data + model[path][0] = not model[path][col] + return + + self.plugin_list_col_0 = gtk.TreeViewColumn('Use') + self.plugin_list_col_0.pack_start(self.plugin_rend_toggle, False) + self.plugin_list_col_0.set_cell_data_func(self.plugin_rend_toggle, plugin_rend_toggle_func) + self.plugin_rend_toggle.connect("toggled", plugin_rend_toggle_cb, (self.plugin_list_store, 0)) + + self.plugin_list_col_1 = gtk.TreeViewColumn('Name') + self.plugin_list_col_1.pack_start(self.plugin_rend_text, True) + self.plugin_list_col_1.set_cell_data_func(self.plugin_rend_text, plugin_rend_text_func, 1) + + self.plugin_list_col_2 = gtk.TreeViewColumn('Description') + self.plugin_list_col_2.pack_start(self.plugin_rend_text, True) + self.plugin_list_col_2.set_cell_data_func(self.plugin_rend_text, plugin_rend_text_func, 2) + + self.plugin_list_col_3 = gtk.TreeViewColumn('Parameters') + self.plugin_list_col_3.pack_start(self.plugin_rend_text, True) + self.plugin_list_col_3.set_cell_data_func(self.plugin_rend_text, plugin_rend_text_func, 3) + + self.plugin_list.append_column(self.plugin_list_col_0) + self.plugin_list.append_column(self.plugin_list_col_1) + self.plugin_list.append_column(self.plugin_list_col_2) + self.plugin_list.append_column(self.plugin_list_col_3) + self.plugin_list.set_search_column(1) + + pluginsystem = tasker.pluginsystem() + self.plugin_iter = {} + self.flow_list_data = set() + + #flow combobox + for plname in pluginsystem.list(): + p = pluginsystem.getplugin(plname) + piter = self.plugin_list_store.append(None, [False, "%s (%s)" % (p.name, p.version), p.description, "", plname]) + self.plugin_iter[plname] = piter + for n,d in [ (f, p.getFlow(f).description) for f in p.getFlows() ]: + self.plugin_list_store.append(piter, [False, n, d, "", plname]) + self.flow_list_data.add(n) + + self.flow_list_rend_text = gtk.CellRendererText() + self.flow_list_store = gtk.ListStore(gobject.TYPE_STRING) + self.flow_list_store_diagnose = -1 + for idx,n in enumerate(sorted(self.flow_list_data)): + self.flow_list_store.append([n]) + if n=="diagnose": + self.flow_list_store_diagnose = idx + self.flow_list = self._glade.get_widget("combo_Advanced_Flows") + self.flow_list.set_model(self.flow_list_store) + self.flow_list.pack_start(self.flow_list_rend_text, True) + self.flow_list.add_attribute(self.flow_list_rend_text, 'text', 0) + self.flow_list.set_active(self.flow_list_store_diagnose) + + # results + self.result_list_store = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_INT) + self.result_list = self._glade.get_widget("tree_Results") + self.result_list.set_model(self.result_list_store) + self.result_list_iter = {} + + def result_rend_text_func(column, cell_renderer, tree_model, iter, user_data): + colors = [ + gtk.gdk.Color(red=50000, green=50000, blue=50000), + gtk.gdk.Color(red=10000, green=50000, blue=10000), + gtk.gdk.Color(red=50000, green=10000, blue=10000), + gtk.gdk.Color(red=10000, green=10000, blue=50000) + ] + state = tree_model.get_value(iter, 3) + + cell_renderer.set_property("cell-background-set", True) + cell_renderer.set_property("cell-background-gdk", colors[state]) + + if user_data==2 and state!=2: + cell_renderer.set_property("foreground-set", True) + cell_renderer.set_property("foreground-gdk", gtk.gdk.Color(red=40000, green=40000, blue=40000)) + else: + cell_renderer.set_property("foreground-set", False) + + cell_renderer.set_property("text", tree_model.get_value(iter, user_data)) + return + + self.result_rend_text = gtk.CellRendererText() + + self.result_list_col_0 = gtk.TreeViewColumn('Name') + self.result_list_col_0.pack_start(self.result_rend_text, True) + self.result_list_col_0.set_cell_data_func(self.result_rend_text, result_rend_text_func, 0) + + self.result_list_col_1 = gtk.TreeViewColumn('Status') + self.result_list_col_1.pack_start(self.result_rend_text, True) + self.result_list_col_1.set_cell_data_func(self.result_rend_text, result_rend_text_func, 1) + + self.result_list_col_2 = gtk.TreeViewColumn('Description') + self.result_list_col_2.pack_start(self.result_rend_text, True) + self.result_list_col_2.set_cell_data_func(self.result_rend_text, result_rend_text_func, 2) + + self.result_list_col_3 = gtk.TreeViewColumn('Status ID') + self.result_list_col_3.pack_start(self.result_rend_text, True) + self.result_list_col_3.add_attribute(self.result_rend_text, 'text', 3) + self.result_list_col_3.set_property("visible", False) + + self.result_list.append_column(self.result_list_col_0) + self.result_list.append_column(self.result_list_col_1) + self.result_list.append_column(self.result_list_col_2) + self.result_list.append_column(self.result_list_col_3) + self.result_list.set_search_column(0) + + def update(self, message): + def _o(func, *args, **kwargs): + """Always return False -> remove from the idle queue after first execution""" + func(*args, **kwargs) + return False + + def issue_state(self): + if self._fixed: + return ("Fixed", 3) + elif self._happened and self._detected: + return ("Detected", 2) + elif self._detected: + return ("No problem", 1) + else: + return ("Waiting for check", 0) + + + if self._cfg.operation.verbose == "True": + self._importance = logging.DEBUG + else: + self._importance = logging.INFO + + """Read the reporting system message and schedule a call to update stuff in the gui using gobject.idle_add(_o, func, params...)""" + if message["action"]==reporting.END: + gobject.idle_add(_o, self._window.destroy) + elif message["action"]==reporting.QUESTION: + print "FIXME: Questions not implemented yet" + elif message["action"]==reporting.START: + if self._importance<=message["importance"]: + ctx = self.status_text.get_context_id(message["origin"].name) + gobject.idle_add(_o, self.status_text.push, ctx, "START: %s (%s)" % (message["origin"].name, message["message"])) + elif message["action"]==reporting.STOP: + if self._importance<=message["importance"]: + ctx = self.status_text.get_context_id(message["origin"].name) + gobject.idle_add(_o, self.status_text.push, ctx, "STOP: %s (%s)" % (message["origin"].name, message["message"])) + elif message["action"]==reporting.PROGRESS: + if self._importance<=message["importance"]: + if message["message"] is None: + gobject.idle_add(self.status_progress.hide) + else: + gobject.idle_add(_o, self.status_progress.set_text, "%d/%d - %s" % (message["message"][0], message["message"][1], message["origin"].name)) + gobject.idle_add(_o, self.status_progress.set_fraction, float(message["message"][0])/message["message"][1]) + gobject.idle_add(_o, self.status_progress.show) + elif message["action"]==reporting.INFO: + if self._importance<=message["importance"]: + ctx = self.status_text.get_context_id(message["origin"].name) + gobject.idle_add(_o, self.status_text.push, ctx, "INFO: %s (%s)" % (message["message"], message["origin"].name)) + elif message["action"]==reporting.ALERT: + if self._importance<=message["importance"]: + ctx = self.status_text.get_context_id(message["origin"].name) + gobject.idle_add(_o, self.status_text.push, ctx, "ALERT: %s (%s)" % (message["message"], message["origin"].name)) + elif message["action"]==reporting.EXCEPTION: + ctx = self.status_text.get_context_id(message["origin"].name) + gobject.idle_add(_o, self.status_text.push, ctx, "EXCEPTION: %s (%s)" % (message["message"], message["origin"].name)) + elif message["action"]==reporting.TABLE: + if self._importance<=message["importance"]: + print "TABLE %s FROM %s" % (message["title"], message["origin"].name,) + pprint.pprint(message["message"]) + elif message["action"]==reporting.TREE: + if self._importance<=message["importance"]: + print "TREE %s FROM %s" % (message["title"], message["origin"].name,) + pprint.pprint(message["message"]) + elif message["action"]==reporting.ISSUE: + i = message["message"] + t,ids = issue_state(i) + if not self.result_list_iter.has_key(i): + self.result_list_iter[i] = self.result_list_store.append([i.name, t, i.description, ids]) + else: + for idx,val in enumerate([i.name, t, i.description, ids]): + gobject.idle_add(_o, self.result_list_store.set, self.result_list_iter[i], idx, val) + else: + print "FIXME: Unknown message action %d!!" % (message["action"],) + print message + + def run(self): + gtk.gdk.threads_init() + gtk.main() + +class FlagList(object): + def __init__(self, cfg, flags, dir=""): + self._glade = gtk.glade.XML(os.path.join(dir, "firstaidkit.glade"), "FlagList") + self._window = self._glade.get_widget("FlagList") + self._window.set_modal(True) + self.flags = {} + self._cb = CallbacksFlagList(self._window, cfg, self.flags) + self._glade.signal_autoconnect(self._cb) + fl_gui = self._glade.get_widget("box_flags") + flags_set = cfg.operation._list("flags") + for f in sorted(flags.known()): + b = gtk.CheckButton(label=f) + self.flags[f] = b + b.set_active(f in flags_set) + b.show() + fl_gui.pack_start(b, expand=False, fill=True) + l = gtk.Label("") + l.show() + + fl_gui.pack_end(l, expand=True, fill=True) + +class PluginInfo(object): + def close(self, widget): + self._window.destroy() + + def __init__(self, plugin, dir=""): + self._glade = gtk.glade.XML(os.path.join(dir, "firstaidkit.glade"), "PluginInfo") + self._window = self._glade.get_widget("PluginInfo") + self._window.set_modal(True) + self._window.set_title(self._window.get_title()+plugin.name) + + self._close = self._glade.get_widget("CloseButton") + self._close.connect("clicked", self.close) + + self._name = self._glade.get_widget("Info_name") + self._name.set_label(plugin.name) + + self._version = self._glade.get_widget("Info_version") + self._version.set_label(plugin.version) + + self._author = self._glade.get_widget("Info_author") + self._author.set_label(plugin.author) + + self._description = self._glade.get_widget("Info_description") + self._description.set_label(plugin.description) + + self._table = self._glade.get_widget("Table") + + for n,d in [ (f, plugin.getFlow(f).description) for f in plugin.getFlows() ]: + if n==plugin.default_flow: + lname = gtk.Label(""+n+"") + lname.set_property("use-markup", True) + else: + lname = gtk.Label(n) + lname.show() + ldesc = gtk.Label(d) + ldesc.show() + ldesc.set_line_wrap(True) + + sy = self._table.get_property("n-rows") + sx = self._table.get_property("n-columns") + sy += 1 + self._table.resize(sy, sx) + self._table.attach(lname, 0, 1, sy-1, sy, yoptions = gtk.FILL, xoptions = gtk.FILL) + lname.set_alignment(0, 0) + self._table.attach(ldesc, 1, 2, sy-1, sy, yoptions = gtk.FILL, xoptions = gtk.FILL) + ldesc.set_alignment(0, 0) + + +if __name__=="__main__": + w = MainWindow(None, None, None) + w.run() + -- cgit