Baby Language Lab Scripts
A collection of data processing tools.
 All Classes Namespaces Files Functions Variables Pages
output_window.py
Go to the documentation of this file.
1 from gi.repository import Gtk as gtk
2 
3 from utils.ui_utils import UIUtils
4 from utils.filters_frame import FiltersFrame
5 from db.bll_database import DBConstants
6 from data_structs.output_calcs import *
7 from data_structs.output import Output
8 
9 class OutputWindow():
10  def __init__(self, action_callback, edit_output=None):
11  self.action_callback = action_callback
12  self.edit_output = edit_output
13 
14  self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
15  self.window.set_title('%s Output' % ('Edit' if self.edit_output else 'Create'))
16  self.window.set_border_width(10)
17  self.window.set_default_size(400, 400)
18 
19  vbox = gtk.VBox()
20  #table = gtk.Table(10, 10)
21  grid = gtk.Grid()
22 
23  props_frame = self._build_props_frame()
24  filters_frame = FiltersFrame(existing_filters=edit_output.filters if edit_output else [])
25  filters_frame.set_vexpand(True)
26  options_frame = OptionsFrame(edit_output)
27  options_frame.set_vexpand(True)
28 
29  vbox.pack_start(props_frame, False, False, 0)
30  #table.attach(filters_frame, 0, 5, 2, 10, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL)
31 
32  grid.attach(filters_frame, 0, 0, 1, 1)
33  #table.attach(options_frame, 6, 10, 2, 10, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL)
34 
35  grid.attach(options_frame, 1, 0, 1, 1)
36  vbox.pack_start(grid, True, True, 0)
37 
38  button_box = gtk.HButtonBox()
39  button_box.set_layout(gtk.ButtonBoxStyle.EDGE)
40 
41  cancel_button = gtk.Button(stock=gtk.STOCK_CANCEL)
42  cancel_button.connect('clicked', lambda w: self.window.destroy())
43  button_box.pack_start(cancel_button, False, False, 0)
44 
45  ok_button = gtk.Button(stock=gtk.STOCK_OK)
46  button_box.pack_start(ok_button, False, False, 0)
47  ok_button.connect('clicked', lambda w: self._create_output(filters_frame, options_frame))
48  vbox.pack_end(button_box, False, False, 0)
49 
50  self.window.add(vbox)
51  self.window.show_all()
52 
53  def _build_props_frame(self):
54  props_frame = gtk.Frame(label='Properties')
55  #sub_table = gtk.Table(2, 2)
56  sub_grid = gtk.Grid()
57  name_label = gtk.Label('Name:')
58  self.name_entry = gtk.Entry()
59  self.name_entry.set_width_chars(50)
60  if self.edit_output:
61  self.name_entry.set_text(self.edit_output.name)
62 
63  #It appears that proper alignment can only be acheived with HBoxes here...
64  name_label_hbox = gtk.HBox()
65  name_label_hbox.pack_start(gtk.Alignment(xalign=1, yalign=0, xscale=0, yscale=0), True, True, 0)
66  name_label_hbox.pack_start(name_label, False, False, 0)
67  #sub_table.attach(name_label_hbox, 0, 1, 0, 1, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL)
68  sub_grid.attach(name_label_hbox, 0, 0, 1, 1)
69 
70  name_entry_hbox = gtk.HBox()
71  name_entry_hbox.pack_start(self.name_entry, False, False, 0)
72  #sub_table.attach(name_entry_hbox, 1, 2, 0, 1, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL)
73  sub_grid.attach(name_entry_hbox, 1, 0, 1, 1)
74 
75  desc_label = gtk.Label('Description:')
76  self.desc_entry = gtk.Entry()
77  self.desc_entry.set_width_chars(50)
78  if self.edit_output:
79  self.desc_entry.set_text(self.edit_output.desc)
80 
81  desc_label_hbox = gtk.HBox()
82  desc_label_hbox.pack_start(gtk.Alignment(xalign=1, yalign=0, xscale=0, yscale=0), True, True, 0)
83  desc_label_hbox.pack_start(desc_label, False, False, 0)
84  #sub_table.attach(desc_label_hbox, 0, 1, 1, 2, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL)
85  sub_grid.attach(desc_label_hbox, 0, 1, 1, 1)
86 
87  desc_entry_hbox = gtk.HBox()
88  desc_entry_hbox.pack_start(self.desc_entry, False, False, 0)
89  #sub_table.attach(desc_entry_hbox, 1, 2, 1, 2, gtk.EXPAND|gtk.FILL, gtk.EXPAND|gtk.FILL, ypadding=3) #ypadding adds some space between bottom of entry and bottom of frame border
90  sub_grid.attach(desc_entry_hbox, 1, 1, 1, 1)
91 
92  props_frame.add(sub_grid)
93 
94  return props_frame
95 
96  def _validate(self):
97  return self.name_entry.get_text() != '' #only name field is required, desc field is optional
98 
99  def _create_output(self, filters_frame, options_frame):
100  if options_frame.validate() and self._validate():
101  name = self.name_entry.get_text()
102  desc = self.desc_entry.get_text()
103  filters = filters_frame.get_filters()
104  calc = options_frame.get_output_calc()
105  chained = options_frame.get_seg_linkage()
106 
107  output = Output(name, desc, filters, calc, chained)
108 
109  self.action_callback(output)
110  self.window.destroy()
111 
112  else:
113  UIUtils.show_empty_form_dialog()
114 
115 class OptionsFrame(gtk.Frame):
116  def __init__(self, edit_output=None, label='Options'):
117  super(OptionsFrame, self).__init__(label=label)
118  self.edit_output = edit_output
119 
120  #The 'seg_linkage' entry is a 2-tuple of the form:
121  #(lambda: True if valid else False, lambda: True if linked else False)
122  #The 'calc_type' entry is a 2-tuple of the form:
123  #(lambda: True if valid else False, lambda: selected value from DBConstants.COMBO_OPTIONS)
124  #Entries in the 'calc_inputs' dictionary should be arrays of the form:
125  #[(lambda: True if valid else False, lambda: returns value of corresponding OutputCalc constructer arg), ...]
126  self.inputs_dict = {'seg_linkage': None,
127  'calc_type': None,
128  'calc_inputs': [],
129  }
130  self._build()
131 
132  def get_output_calc(self):
133  options = DBConstants.COMBO_OPTIONS[DBConstants.COMBO_GROUPS.OUTPUT_CALC_TYPES]
134  calc_type = self.inputs_dict['calc_type'][1]()
135 
136  calc_cls = {options.COUNT: CountOutputCalc,
137  options.RATE: RateOutputCalc,
138  options.TIME_PERIOD: TimePeriodOutputCalc,
139  options.BREAKDOWN: BreakdownOutputCalc,
140  options.LIST: ListOutputCalc,
141  }[calc_type]
142 
143  args = []
144  for arg_tuple in self.inputs_dict['calc_inputs']:
145  args.append(arg_tuple[1]())
146 
147  return calc_cls(*args)
148 
149  def get_seg_linkage(self):
150  return self.inputs_dict['seg_linkage'][1]()
151 
152  def validate(self):
153  is_valid = True
154  inputs = [self.inputs_dict['seg_linkage'], self.inputs_dict['calc_type']] + self.inputs_dict['calc_inputs']
155 
156  i = 0
157  while is_valid and i < len(inputs):
158  is_valid = inputs[i][0]()
159  i += 1
160 
161  return is_valid
162 
163  def _build(self):
164  vbox = gtk.VBox(spacing=5)
165 
166  linkage_label = gtk.Label('Segment Linkage:')
167  linkage_combo = UIUtils.build_options_combo(
168  DBConstants.COMBO_GROUPS.FILTER_LINKAGE_OPTIONS,
169  include_empty_option=False,
170  active_index=(0 if self.edit_output and self.edit_output.chained else 1)
171  )
172 
173  self.inputs_dict['seg_linkage'] = (lambda: linkage_combo.get_active() >= 0, #Note: there is no "empty" option in this combo (hence >=)
174  lambda: linkage_combo.get_model()[linkage_combo.get_active()][1] == DBConstants.COMBO_OPTIONS[DBConstants.COMBO_GROUPS.FILTER_LINKAGE_OPTIONS].LINKED)
175  linkage_hbox = gtk.HBox()
176  linkage_hbox.pack_start(linkage_label, True, True, 0)
177  linkage_hbox.pack_start(linkage_combo, True, True, 0)
178  vbox.pack_start(linkage_hbox, False, False, 0)
179 
180  #table = gtk.Table(2, 2)
181  grid = gtk.Grid()
182  grid.set_row_spacing(5)
183 
184  cell_matrix = []
185  n_rows = 2 #table.get_property('n-rows')
186  n_cols = 2 #table.get_property('n-columns')
187  for row in range(n_rows):
188  for col in range(n_cols):
189  cell_hbox = gtk.HBox()
190  if col == 0:
191  cell_matrix.append([cell_hbox])
192  else:
193  cell_matrix[row].append(cell_hbox)
194 
195  #table.attach(cell_matrix[row][col], col, col + 1, row, row + 1, gtk.EXPAND, gtk.EXPAND)
196  grid.attach(cell_matrix[row][col], col, row, 1, 1)
197 
198  output_type_label = gtk.Label('Output Type:')
199 
200  active_index = 0
201  if self.edit_output:
202  active_index = self._get_output_type_combo_index()
203 
204  output_type_combo = UIUtils.build_options_combo(DBConstants.COMBO_GROUPS.OUTPUT_CALC_TYPES, active_index)
205  output_type_combo.connect( 'changed', lambda w: self._update_options_frame(cell_matrix, n_rows, n_cols, w.get_model().get(w.get_active_iter(), 1)[0]) )
206  if self.edit_output:
207  self._update_options_frame( cell_matrix, n_rows, n_cols, output_type_combo.get_model().get(output_type_combo.get_active_iter(), 1)[0] )
208 
209  self.inputs_dict['calc_type'] = (lambda: output_type_combo.get_active() > 0,
210  lambda: output_type_combo.get_model()[output_type_combo.get_active()][1])
211  output_type_hbox = gtk.HBox()
212  output_type_hbox.pack_start(output_type_label, True, True, 0)
213  output_type_hbox.pack_start(output_type_combo, True, True, 0)
214 
215  vbox.pack_start(output_type_hbox, False, False, 0)
216  vbox.pack_start(gtk.HSeparator(), False, False, 0)
217  vbox.pack_start(grid, True, True, 0)
218 
219  self.add(vbox)
220 
222  active_index = 0
223 
224  calc_cls = self.edit_output.output_calc.__class__
225  opts = DBConstants.COMBO_OPTIONS[DBConstants.COMBO_GROUPS.OUTPUT_CALC_TYPES]
226  val = {CountOutputCalc: opts.COUNT,
227  RateOutputCalc: opts.RATE,
228  TimePeriodOutputCalc: opts.TIME_PERIOD,
229  BreakdownOutputCalc: opts.BREAKDOWN,
230  ListOutputCalc: opts.LIST,
231  }[calc_cls]
232 
233  return self._get_combo_index(DBConstants.COMBO_GROUPS.OUTPUT_CALC_TYPES, val)
234 
235  def _get_combo_index(self, combo_group, combo_val):
236  opts = DBConstants.COMBO_OPTIONS[combo_group]
237  i = 0
238  enum_vals = opts.get_ordered_vals()
239  found_index = -1
240  while found_index < 0 and i < len(enum_vals):
241  if enum_vals[i] == combo_val:
242  found_index = i
243  i += 1
244 
245  if found_index > -1:
246  active_index = found_index
247 
248  return active_index
249 
250  #sel_option_id is db_id from combo_options table
251  def _update_options_frame(self, cell_matrix, n_rows, n_cols, sel_option_id):
252  for row in range(n_rows):
253  for col in range(n_cols):
254  children = cell_matrix[row][col].get_children()
255  for child in children:
256  cell_matrix[row][col].remove(child)
257  self.inputs_dict['calc_inputs'] = [] #clear any previous inputs
258  options = DBConstants.COMBO_OPTIONS[DBConstants.COMBO_GROUPS.OUTPUT_CALC_TYPES]
259 
260  if sel_option_id == options.COUNT:
261  regex_label = gtk.Label('Search:')
262  regex_entry = gtk.Entry()
263  regex_entry.set_width_chars(30)
264  if self.edit_output:
265  regex_entry.set_text(self.edit_output.output_calc.search_term)
266 
267  regex_helper = UIUtils.build_regex_helper_combo(regex_entry)
268  hbox = gtk.HBox()
269  hbox.pack_start(regex_entry, False, False, 0)
270  hbox.pack_start(regex_helper, False, False, 0)
271 
272  cell_matrix[0][0].pack_start(regex_label, False, False, 0)
273  cell_matrix[0][1].pack_start(hbox, False, False, 0)
274  cell_matrix[0][0].show_all()
275  cell_matrix[0][1].show_all()
276 
277  combo_label = gtk.Label('Calc Type:')
278  active_index = 0
279  if self.edit_output:
280  active_index = self._get_combo_index(DBConstants.COMBO_GROUPS.COUNT_OUTPUT_CALC_TYPES, self.edit_output.output_calc.count_type)
281 
282  combo = UIUtils.build_options_combo(DBConstants.COMBO_GROUPS.COUNT_OUTPUT_CALC_TYPES, active_index)
283  cell_matrix[1][0].pack_start(combo_label, False, False, 0)
284  cell_matrix[1][1].pack_start(combo, False, False, 0)
285  cell_matrix[1][0].show_all()
286  cell_matrix[1][1].show_all()
287 
288  self.inputs_dict['calc_inputs'].append( (lambda: True, #if no text, assume user is searching for '*'
289  lambda: regex_entry.get_text()) )
290  self.inputs_dict['calc_inputs'].append( (lambda: combo.get_active() > 0,
291  lambda: combo.get_model()[combo.get_active()][1]) )
292 
293  elif sel_option_id == options.RATE:
294  regex_label = gtk.Label('Search:')
295  regex_entry = gtk.Entry()
296  regex_entry.set_width_chars(30)
297  if self.edit_output:
298  regex_entry.set_text(self.edit_output.output_calc.search_term)
299 
300  regex_helper = UIUtils.build_regex_helper_combo(regex_entry)
301  hbox = gtk.HBox()
302  hbox.pack_start(regex_entry, False, False, 0)
303  hbox.pack_start(regex_helper, False, False, 0)
304 
305  cell_matrix[0][0].pack_start(regex_label, False, False, 0)
306  cell_matrix[0][1].pack_start(hbox, False, False, 0)
307  cell_matrix[0][0].show_all()
308  cell_matrix[0][1].show_all()
309 
310  combo_label = gtk.Label('Calc Type:')
311  active_index = 0
312  if self.edit_output:
313  active_index = self._get_combo_index(DBConstants.COMBO_GROUPS.RATE_OUTPUT_CALC_TYPES, self.edit_output.output_calc.rate_type)
314 
315  combo = UIUtils.build_options_combo(DBConstants.COMBO_GROUPS.RATE_OUTPUT_CALC_TYPES, active_index)
316  cell_matrix[1][0].pack_start(combo_label, False, False, 0)
317  cell_matrix[1][1].pack_start(combo, False, False, 0)
318  cell_matrix[1][0].show_all()
319  cell_matrix[1][1].show_all()
320 
321  self.inputs_dict['calc_inputs'].append( (lambda: True,
322  lambda: regex_entry.get_text()) )
323  self.inputs_dict['calc_inputs'].append( (lambda: combo.get_active() > 0,
324  lambda: combo.get_model()[combo.get_active()][1]) )
325 
326  elif sel_option_id == options.TIME_PERIOD:
327  regex_label = gtk.Label('Search:')
328  regex_entry = gtk.Entry()
329  regex_entry.set_width_chars(30)
330  if self.edit_output:
331  regex_entry.set_text(self.edit_output.output_calc.search_term)
332 
333  regex_helper = UIUtils.build_regex_helper_combo(regex_entry)
334  hbox = gtk.HBox()
335  hbox.pack_start(regex_entry, False, False, 0)
336  hbox.pack_start(regex_helper, False, False, 0)
337 
338  cell_matrix[0][0].pack_start(regex_label, False, False, 0)
339  cell_matrix[0][1].pack_start(hbox, False, False, 0)
340  cell_matrix[0][0].show_all()
341  cell_matrix[0][1].show_all()
342 
343  self.inputs_dict['calc_inputs'].append( (lambda: True,
344  lambda: regex_entry.get_text()) )
345 
346  elif sel_option_id == options.BREAKDOWN:
347  row_combo_label = gtk.Label('Row Criteria:')
348  active_row_index = 0
349  if self.edit_output:
350  active_row_index = self._get_combo_index(DBConstants.COMBO_GROUPS.BREAKDOWN_OUTPUT_CALC_CRITERIA, self.edit_output.output_calc.row_criteria)
351 
352  row_combo = UIUtils.build_options_combo(DBConstants.COMBO_GROUPS.BREAKDOWN_OUTPUT_CALC_CRITERIA, active_row_index)
353  cell_matrix[0][0].pack_start(row_combo_label, False, False, 0)
354  cell_matrix[0][1].pack_start(row_combo, False, False, 0)
355  cell_matrix[0][0].show_all()
356  cell_matrix[0][1].show_all()
357 
358  col_combo_label = gtk.Label('Column Criteria:')
359  active_col_index = 0
360  if self.edit_output:
361  active_col_index = self._get_combo_index(DBConstants.COMBO_GROUPS.BREAKDOWN_OUTPUT_CALC_CRITERIA, self.edit_output.output_calc.col_criteria)
362  col_combo = UIUtils.build_options_combo(DBConstants.COMBO_GROUPS.BREAKDOWN_OUTPUT_CALC_CRITERIA, active_col_index)
363  cell_matrix[1][0].pack_start(col_combo_label, False, False, 0)
364  cell_matrix[1][1].pack_start(col_combo, False, False, 0)
365  cell_matrix[1][0].show_all()
366  cell_matrix[1][1].show_all()
367 
368  self.inputs_dict['calc_inputs'].append( (lambda: row_combo.get_active() > 0,
369  lambda: row_combo.get_model()[row_combo.get_active()][1]) )
370  self.inputs_dict['calc_inputs'].append( (lambda: col_combo.get_active() > 0,
371  lambda: col_combo.get_model()[col_combo.get_active()][1]) )
372 
373  elif sel_option_id == options.LIST:
374  regex_label = gtk.Label('Search:')
375  regex_entry = gtk.Entry()
376  regex_entry.set_width_chars(30)
377  if self.edit_output:
378  regex_entry.set_text(self.edit_output.output_calc.search_term)
379 
380  regex_helper = UIUtils.build_regex_helper_combo(regex_entry)
381  hbox = gtk.HBox()
382  hbox.pack_start(regex_entry, False, False, 0)
383  hbox.pack_start(regex_helper, False, False, 0)
384 
385  cell_matrix[0][0].pack_start(regex_label, False, False, 0)
386  cell_matrix[0][1].pack_start(hbox, False, False, 0)
387  cell_matrix[0][0].show_all()
388  cell_matrix[0][1].show_all()
389 
390  grouping_cat_label = gtk.Label("Grouping Category:")
391  active_index = 0
392  if self.edit_output:
393  active_index = self._get_combo_index(DBConstants.COMBO_GROUPS.LIST_OUTPUT_CALC_CATS, self.edit_output.output_calc.cat)
394  grouping_cat_combo = UIUtils.build_options_combo(DBConstants.COMBO_GROUPS.LIST_OUTPUT_CALC_CATS, active_index)
395  cell_matrix[1][0].pack_start(grouping_cat_label, False, False, 0)
396  cell_matrix[1][1].pack_start(grouping_cat_combo, False, False, 0)
397  cell_matrix[1][0].show_all()
398  cell_matrix[1][1].show_all()
399 
400  self.inputs_dict['calc_inputs'].append( (lambda: True,
401  lambda: regex_entry.get_text()) )
402  self.inputs_dict['calc_inputs'].append( (lambda: grouping_cat_combo.get_active() > 0,
403  lambda: grouping_cat_combo.get_model()[grouping_cat_combo.get_active()][1]) )
404 
405  #Make sure we don't try to restore state on subsequent calls to this method
406  #Note: outdated outputs will be removed from the database by create_config_window.py
407  self.edit_output = None