import repository from arizona
[raven.git] / apps / kong-ui / kong_ui.py
1 #!/usr/bin/env python
2
3 """
4 kong_ui.py
5
6 This file contains the KongUIApplication class. It is the main view controller for
7 the user interface. It creates the GTK window, loads the layout from a markup file,
8 sets up connections to events, and handles various events. With the assistance of
9 the KongController class, it interfaces with the kong application and outputs
10 to various widgets on the screen.
11
12 author: Charles Magahern <charles.magahern@arizona.edu>
13 date:   4/1/2011
14 """
15
16 import sys
17 import os
18 import threading
19 import time
20 import gtk
21 import gobject
22 import ctypes
23
24 from kong_controller import KongController
25
26 (
27     COLUMN_ADDRESS,
28     COLUMN_AGE,
29     COLUMN_STATUS
30 ) = range(3)
31
32 (
33     BUTTON_RUN_STATE,
34     BUTTON_STOP_STATE
35 ) = range(2)
36
37 class KongUIApplication:
38     GTK_BUILDER_FILE = "kong_layout.glade"
39     KONG_REPORT_PAGENUM = 1
40     
41     def __init__(self, parent=None):
42         self.kcontroller = None         # The KongController object
43         self.report_dirty = True        # Stores whether or not the report needs to be re-generated
44         
45         self.window = None              # The main GTK window object
46         self.status_list_view = None    # List view for the status of various slices
47         self.console_textbuffer = None  # Textbuffer for the console near the bottom of the window
48         self.report_textbuffer = None   # The report screen's textbuffer
49         self.run_stop_button = None     # Run/Stop Button
50         self.reset_button = None        # Reset Button
51         self.run_stop_label = None      # Run/Stop Label inside of the Run/Stop button
52         self.run_stop_state = BUTTON_RUN_STATE  # The state of the run/stop button, which does two different things
53         self.machine_quantity_spin_btn = None   # Machine quantity textfield with buttons collectively called spinbutton
54         self.status_label = None        # Current Status Label
55         self.console_textview = None    # View object for the console widget
56         
57         if (len(sys.argv) >= 2):
58             path = sys.argv[1]
59         else:
60             path = os.getcwd()
61
62         builder = gtk.Builder()
63         builder.add_from_file(self.GTK_BUILDER_FILE)
64         builder.connect_signals(self)
65         
66         gobject.threads_init()
67         
68         # All of the widgets are laid out in a markup file loaded with the gtk.Builder object
69         self.window = builder.get_object("main_window")
70         self.status_list_view = builder.get_object("status_list_view")
71         self.console_textbuffer = builder.get_object("console_textbuffer")
72         self.report_textbuffer = builder.get_object("report_textbuffer")
73         self.run_stop_button = builder.get_object("run_stop_button")
74         self.reset_button = builder.get_object("reset_button")
75         self.run_stop_label = builder.get_object("run_stop_button_label")
76         self.machine_quantity_spin_btn = builder.get_object("machine_quantity_spin_button")
77         self.status_label = builder.get_object("status_label")
78         self.console_textview = builder.get_object("console_textview")
79         
80         self.report_textbuffer.set_text("---No report has been generated yet.---")
81         
82         self.machine_quantity_spin_btn.set_range(1, 1000)
83         self.machine_quantity_spin_btn.set_increments(1, 10)
84         
85         self.setup_statuslist_model()
86         self.setup_statuslist_columns()
87         
88         self.window.show()
89         
90         # Check if valid directory first, then start
91         self.kcontroller = KongController(path, self)
92         valid_dir = self.kcontroller.check_directory()
93         if (not valid_dir):
94             self.run_stop_button.set_sensitive(False)
95             self.reset_button.set_sensitive(False)
96             
97             dialog = gtk.MessageDialog(self.window, 
98                                        gtk.DIALOG_DESTROY_WITH_PARENT,
99                                        gtk.MESSAGE_ERROR,
100                                        gtk.BUTTONS_OK,
101                                        "The directory specified does not appear to contain a valid experiment.")
102             dialog.run()
103             dialog.destroy()
104             
105             # It's not good to exit the event loop when not inside the GTK main loop. Defer the quit message until
106             # the main loop is able to quit
107             self.window.connect('event-after', gtk.main_quit)
108         else:
109             self.update_status()
110         
111     
112     ## Configuration and Setup Methods ##
113     
114     # Sets up the list view containing a list of all of the slices.
115     def setup_statuslist_model(self):
116         liststore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING)
117         self.status_list_view.set_model(liststore)
118     
119     # Sets up each of the columns of the status list view
120     def setup_statuslist_columns(self):
121         addr_column = gtk.TreeViewColumn("Address", gtk.CellRendererText(), text=COLUMN_ADDRESS)
122         age_column = gtk.TreeViewColumn("Age", gtk.CellRendererText(), text=COLUMN_AGE)
123         state_column = gtk.TreeViewColumn("State", gtk.CellRendererText(), text=COLUMN_STATUS)
124         
125         addr_column.set_resizable(True)
126         addr_column.set_min_width(300)
127         addr_column.set_clickable(True)
128         addr_column.set_sort_column_id(COLUMN_ADDRESS)
129         age_column.set_resizable(False)
130         age_column.set_clickable(True)
131         age_column.set_sort_column_id(COLUMN_AGE)
132         state_column.set_resizable(False)
133         
134         self.status_list_view.append_column(addr_column)
135         self.status_list_view.append_column(age_column)
136         self.status_list_view.append_column(state_column)
137     
138     def set_run_button_state(self, state):
139         if (state == BUTTON_RUN_STATE):
140             self.run_stop_label.set_markup("<b>Run</b>")
141         elif (state == BUTTON_STOP_STATE):
142             self.run_stop_label.set_markup("<b>Stop</b>")
143             
144         self.run_stop_state = state
145     
146     
147     ## Helper Methods ##
148     
149     def get_number_of_machines(self):
150         return self.machine_quantity_spin_btn.get_value_as_int()
151     
152     
153     ## Update Methods ##
154     
155     def update_status(self):
156         self.kcontroller.get_experiment_status()
157         
158     def update_statuslist(self, status_rows):
159         liststore = self.status_list_view.get_model()
160         liststore.clear()
161         
162         for row in status_rows:
163             if (len(row) >= 3):
164                 liststore.append([row['addr'], int(row['age']), row['state']])
165
166     def append_console(self, output):
167         end_iter = self.console_textbuffer.get_end_iter()
168         self.console_textbuffer.insert(end_iter, "\n" + output)
169         self.console_textview.scroll_to_mark(self.console_textbuffer.get_insert(), 0)
170     
171     
172     ## Delegate Methods ##
173     # These methods are called from the KongController class when the KongController
174     # finishes a task or requires interaction with the UI in some way.
175     
176     def set_status(self, status):
177         self.status_label.set_markup("<b>" + status.strip() + "</b>")
178         self.window.queue_draw()
179     
180     def append_log(self, message):
181         self.append_console(message)
182         
183     def get_status_callback(self, result):
184         self.update_statuslist(result['status'])
185         self.append_console(result['raw_output'])
186         self.set_status("kong is idle")
187     
188     def run_callback(self):
189         self.update_status()
190         
191         self.set_run_button_state(BUTTON_RUN_STATE)
192         self.reset_button.set_sensitive(True)
193         self.report_dirty = True
194     
195     def abort_callback(self):
196         self.set_run_button_state(BUTTON_RUN_STATE)
197         self.reset_button.set_sensitive(True)
198         self.run_stop_button.set_sensitive(True)
199         
200         self.update_status()
201     
202     def reset_callback(self, result):
203         self.append_console(result['raw_output'])
204         self.update_status()
205         
206     def generate_report_callback(self, result):
207         self.report_textbuffer.set_text(result)
208         self.report_dirty = False
209     
210     
211     ## Action Methods ##
212     
213     def run_stop_action(self, widget, data=None):
214         if (self.run_stop_state == BUTTON_RUN_STATE):
215             self.run_action(widget, data)
216         elif (self.run_stop_state == BUTTON_STOP_STATE):
217             self.stop_action(widget, data)
218         
219     
220     def run_action(self, widget, data=None):
221         self.set_run_button_state(BUTTON_STOP_STATE)
222         self.reset_button.set_sensitive(False)
223         self.set_status("kong is preparing to run")
224         
225         num_machines = self.get_number_of_machines()
226         self.kcontroller.run_experiment(num_machines)
227     
228     def stop_action(self, widget, data=None):
229         self.set_status("Attempting to abort...")
230         self.kcontroller.abort_experiment()
231     
232     def reset_action(self, widget, data=None):
233         self.set_status("Resetting...")
234         self.kcontroller.reset_experiment()
235     
236     # Called when the user switches between tabs in the "notebook" view
237     # (What kind of name is that?)
238     def notebook_switch_page_action(self, notebook, page, page_num):
239         if (page_num == self.KONG_REPORT_PAGENUM and self.report_dirty):
240             self.report_textbuffer.set_text("Fetching Report...")
241             num_machines = self.get_number_of_machines()
242             self.kcontroller.generate_report(num_machines)
243         
244     def gtk_main_quit(self, widget, data=None):
245         gtk.main_quit()
246
247
248     ## Main ##
249     
250     def main(self):
251         gtk.main()
252
253
254 if __name__ == "__main__":
255     kongapp = KongUIApplication()
256     kongapp.main()
257