~launchpad-p-s/sofastatistics/main

« back to all changes in this revision

Viewing changes to dimtree.py

  • Committer: Grant Paton-Simpson
  • Date: 2009-05-19 04:21:43 UTC
  • Revision ID: g@ubuntu-20090519042143-p561mbokz3inefvd
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
import wx
 
3
 
 
4
import dimtables
 
5
import util
 
6
import make_table
 
7
import getdata
 
8
import table_entry
 
9
import pprint
 
10
 
 
11
SORT_OPT_NONE = 0 #No sorting options
 
12
SORT_OPT_BY_LABEL = 1 #Only provide option of sorting by label
 
13
SORT_OPT_ALL = 2 #Option of sorting by labels and freqs
 
14
 
 
15
 
 
16
class DimTree(object):
 
17
    
 
18
    # dimension (rows/columns) trees
 
19
    """
 
20
    All methods which add items to the tree must at the same
 
21
    time attach an ItemConfig object as its PyData (using
 
22
    setInitialConfig().  This includes OnColConfig when 
 
23
    col_no_vars_item is added.
 
24
    """
 
25
    def OnRowItemActivated(self, event):
 
26
        "Activated row item in tree.  Show config dialog."
 
27
        self.ConfigRow()
 
28
    
 
29
    def OnColItemActivated(self, event):
 
30
        "Activated col item in tree.  Show config dialog."
 
31
        self.ConfigCol()
 
32
    
 
33
    def OnRowItemRightClick(self, event):
 
34
        ""
 
35
        self.ShowVarProperties(self.rowtree, event)
 
36
 
 
37
    def OnColItemRightClick(self, event):
 
38
        ""
 
39
        self.ShowVarProperties(self.coltree, event)
 
40
                
 
41
    def ShowVarProperties(self, tree, event):
 
42
        choice_item = tree.GetItemText(event.GetItem())
 
43
        # get val_dic for variable (if any) and display in editable list
 
44
        data = []
 
45
        var_name, var_label = make_table.extractVarDets(choice_item)
 
46
        if self.val_dics.get(var_name):
 
47
            val_dic = self.val_dics.get(var_name)
 
48
            if val_dic:
 
49
                for key, value in val_dic.items():
 
50
                    data.append((str(key), str(value)))
 
51
        new_grid_data = []
 
52
        # get new_grid_data back updated
 
53
        bolnumeric = self.flds[var_name][getdata.FLD_BOLNUMERIC]
 
54
        boldecimal = self.flds[var_name][getdata.FLD_DECPTS]
 
55
        if bolnumeric:
 
56
            if boldecimal:
 
57
                val_type = table_entry.COL_FLOAT
 
58
            else:
 
59
                val_type = table_entry.COL_INT
 
60
        else:
 
61
            val_type = table_entry.COL_STR
 
62
        title = "Settings for %s" % choice_item
 
63
        notes = self.var_notes.get(var_name, "")
 
64
        var_desc = [var_label, notes]
 
65
        getsettings = GetSettings(title, var_desc, data, new_grid_data, 
 
66
                                  val_type)
 
67
        ret = getsettings.ShowModal()
 
68
        if ret == wx.ID_OK:
 
69
            # var label
 
70
            self.var_labels[var_name] = var_desc[0]
 
71
            # var notes
 
72
            self.var_notes[var_name] = var_desc[1]
 
73
            # val dics
 
74
            new_val_dic = {}
 
75
            new_data_rows_n = len(new_grid_data)
 
76
            for i in range(new_data_rows_n):
 
77
                key, value = new_grid_data[i]
 
78
                new_val_dic[key] = value
 
79
            self.val_dics[var_name] = new_val_dic
 
80
            # update lbl file
 
81
            f = file(self.fil_labels, "w")
 
82
            f.write("\nvar_labels=" + pprint.pformat(self.var_labels))
 
83
            f.write("\nvar_notes=" + pprint.pformat(self.var_notes))
 
84
            f.write("\n\nval_dics=" + pprint.pformat(self.val_dics))
 
85
            f.close()
 
86
            # update var label in tree and update demo html
 
87
            tree.SetItemText(event.GetItem(), 
 
88
                    make_table.getVarItem(self.var_labels, var_name))
 
89
            self.UpdateDemoDisplay()
 
90
        
 
91
    def OnRowAdd(self, event):
 
92
        "Add row var under root"
 
93
        self.TryAdding(tree=self.rowtree, root=self.rowRoot, 
 
94
                       dim=dimtables.ROWDIM, oth_dim=dimtables.COLDIM, 
 
95
                       oth_dim_tree=self.coltree, 
 
96
                       oth_dim_root=self.colRoot)
 
97
     
 
98
    def OnColAdd(self, event):
 
99
        "Add column var under root"
 
100
        self.TryAdding(tree=self.coltree, root=self.colRoot, 
 
101
                       dim=dimtables.COLDIM, oth_dim=dimtables.ROWDIM, 
 
102
                       oth_dim_tree=self.rowtree, 
 
103
                       oth_dim_root=self.rowRoot)
 
104
    
 
105
    def TryAdding(self, tree, root, dim, oth_dim, oth_dim_tree, 
 
106
                  oth_dim_root):
 
107
        "Try adding a variable"
 
108
        choice_var_names = self.flds.keys()
 
109
        choices = [make_table.getVarItem(self.var_labels, x) \
 
110
                   for x in choice_var_names]
 
111
        choices.sort(key=lambda s: s.upper()) # sort case insensitive
 
112
        # http://www.python.org/doc/faq/programming/...
 
113
        # ...#i-want-to-do-a-complicated-sort-can-you-do-a-schwartzian-transform-in-python
 
114
        dlg = wx.MultiChoiceDialog(self, "Select a variable", 
 
115
                                    "Variables", choices=choices)
 
116
        if dlg.ShowModal() == wx.ID_OK:
 
117
            # only use in one dimension
 
118
            text_selected = [choices[x] for x in dlg.GetSelections()]
 
119
            for text in text_selected:
 
120
                used_in_oth_dim = self.UsedInOthDim(text, oth_dim_tree, 
 
121
                                                    oth_dim_root)
 
122
                if used_in_oth_dim:
 
123
                    wx.MessageBox("Variable '%s' has already been used in " % \
 
124
                                  text + "%s dimension" % oth_dim)
 
125
                    return
 
126
                # in raw tables, can only use once
 
127
                if self.tab_type == make_table.RAW_DISPLAY:
 
128
                    used_in_this_dim = self.UsedInThisDim(text, tree, root)
 
129
                    if used_in_this_dim:
 
130
                        wx.MessageBox("Variable '%s' cannot be used more than once" \
 
131
                                      % text)
 
132
                        return
 
133
                elif self.tab_type == make_table.ROW_SUMM \
 
134
                        and tree == self.rowtree:
 
135
                    # check it is not numeric (and make sure it lacks a label)
 
136
                    var_name, _ = make_table.extractVarDets(text)                
 
137
                    if not self.flds[var_name][getdata.FLD_BOLNUMERIC] or \
 
138
                            var_name in self.val_dics:
 
139
                        wx.MessageBox("Variable '%s' is not numeric" % text)
 
140
                        return
 
141
            # they all passed the tests so proceed
 
142
            for text in text_selected:
 
143
                new_id = tree.AppendItem(root, text)
 
144
                var_name, _ = make_table.extractVarDets(text)
 
145
                self.setInitialConfig(tree, dim, new_id, var_name)
 
146
            if text_selected:
 
147
                tree.UnselectAll() # multiple
 
148
                tree.SelectItem(new_id)
 
149
                self.UpdateDemoDisplay()
 
150
    
 
151
    def setInitialConfig(self, tree, dim, new_id, var_name):
 
152
        "Set initial config for new item"
 
153
        item_conf = make_table.ItemConfig()
 
154
        if (self.tab_type == make_table.COL_MEASURES \
 
155
                    and dim == dimtables.COLDIM):
 
156
            item_conf.measures_lst = \
 
157
                [make_table.get_default_measure(make_table.COL_MEASURES)]
 
158
        elif (self.tab_type == make_table.ROW_SUMM \
 
159
                    and dim == dimtables.ROWDIM):
 
160
            item_conf.measures_lst = \
 
161
                [make_table.get_default_measure(make_table.ROW_SUMM)]
 
162
        item_conf.bolnumeric = self.flds[var_name][getdata.FLD_BOLNUMERIC]
 
163
        tree.SetItemPyData(new_id, item_conf)
 
164
        tree.SetItemText(new_id, item_conf.getSummary(), 1)
 
165
    
 
166
    def OnRowAddUnder(self, event):
 
167
        """
 
168
        Add row var under another row var (i.e. nest it).
 
169
        Remove measures from ancestors.
 
170
        """
 
171
        tree = self.rowtree
 
172
        root = self.rowRoot
 
173
        dim = dimtables.ROWDIM
 
174
        oth_dim = dimtables.COLDIM
 
175
        oth_dim_tree = self.coltree
 
176
        oth_dim_root = self.colRoot
 
177
        selected_ids = tree.GetSelections()
 
178
        if root not in selected_ids \
 
179
                and self.tab_type != make_table.COL_MEASURES:
 
180
            wx.MessageBox("Rows can only be nested in column " + \
 
181
                              "measures tables")
 
182
            return
 
183
        if len(selected_ids) == 1:
 
184
            self.TryAddingUnder(tree, root, dim, oth_dim, selected_ids[0], 
 
185
                                oth_dim_tree, oth_dim_root)
 
186
        elif len(selected_ids) == 0:
 
187
            wx.MessageBox("Select a %s variable first" % dim)
 
188
            return
 
189
        else:
 
190
            wx.MessageBox("Can only add under a single selected item.")
 
191
            return
 
192
    
 
193
    def OnColAddUnder(self, event):
 
194
        """
 
195
        Add column var under another column var (i.e. nest it).
 
196
        Remove measures from ancestors.
 
197
        """
 
198
        tree = self.coltree
 
199
        root = self.colRoot
 
200
        dim = dimtables.COLDIM
 
201
        oth_dim = dimtables.ROWDIM
 
202
        oth_dim_tree = self.rowtree
 
203
        oth_dim_root = self.rowRoot
 
204
        selected_ids = tree.GetSelections()
 
205
        if len(selected_ids) == 1:
 
206
            self.TryAddingUnder(tree, root, dim, oth_dim, selected_ids[0], 
 
207
                                oth_dim_tree, oth_dim_root)
 
208
        elif len(selected_ids) == 0:
 
209
            wx.MessageBox("Select a %s variable first" % dim)
 
210
            return
 
211
        else:
 
212
            wx.MessageBox("Can only add under a single selected item.")
 
213
            return
 
214
        
 
215
    def TryAddingUnder(self, tree, root, dim, oth_dim, selected_id, 
 
216
                       oth_dim_tree, oth_dim_root):
 
217
        """
 
218
        Try to add var under selected var.
 
219
        Only do so if OK e.g. no duplicate text in either dim.
 
220
        """
 
221
        choice_var_names = self.flds.keys()
 
222
        choices = [make_table.getVarItem(self.var_labels, x) \
 
223
                   for x in choice_var_names]
 
224
        choices.sort(key=lambda s: s.upper())
 
225
        dlg = wx.MultiChoiceDialog(self, "Select a variable", 
 
226
                                   "Variables", choices=choices)
 
227
        if dlg.ShowModal() == wx.ID_OK:
 
228
            text_selected = [choices[x] for x in dlg.GetSelections()]
 
229
            for text in text_selected:
 
230
                # a text label supplied cannot be in any ancestors
 
231
                ancestor_labels = []
 
232
                parent_text = tree.GetItemText(selected_id)
 
233
                ancestor_labels.append(parent_text)
 
234
                ancestors = util.getTreeAncestors(tree, selected_id)
 
235
                parent_ancestor_labels = [tree.GetItemText(x) for \
 
236
                                          x in ancestors]
 
237
                ancestor_labels += parent_ancestor_labels
 
238
                # text cannot be anywhere in other dim tree
 
239
                used_in_oth_dim = self.UsedInOthDim(text, oth_dim_tree, 
 
240
                                                    oth_dim_root)                
 
241
                if text in ancestor_labels:
 
242
                    wx.MessageBox("Variable %s cannot be an " % text + \
 
243
                                  "ancestor of itself" )
 
244
                    return
 
245
                elif used_in_oth_dim:
 
246
                    wx.MessageBox("Variable %s already used in %s dimension" \
 
247
                                  % (text, oth_dim))
 
248
                    return
 
249
            # they all passed the test so proceed        
 
250
            for text in text_selected:
 
251
                new_id = tree.AppendItem(selected_id, text)
 
252
                var_name, _ = make_table.extractVarDets(text)
 
253
                self.setInitialConfig(tree, dim, new_id, var_name)
 
254
                # empty all measures from ancestors and ensure sorting 
 
255
                # is appropriate
 
256
                for ancestor in util.getTreeAncestors(tree, new_id):
 
257
                    item_conf = tree.GetItemPyData(ancestor)
 
258
                    if item_conf: #ignore root node
 
259
                        item_conf.measures_lst = []
 
260
                        if item_conf.sort_order in \
 
261
                            [dimtables.SORT_FREQ_ASC, 
 
262
                             dimtables.SORT_FREQ_DESC]:
 
263
                            item_conf.sort_order = dimtables.SORT_NONE
 
264
                        tree.SetItemText(ancestor, 
 
265
                                         item_conf.getSummary(), 1)                        
 
266
            if text_selected:
 
267
                tree.ExpandAll(root)
 
268
                tree.UnselectAll() # multiple
 
269
                tree.SelectItem(new_id)
 
270
                self.UpdateDemoDisplay()
 
271
    
 
272
    def UsedInOthDim(self, text, oth_dim_tree, oth_dim_root):
 
273
        "Is this variable used in the other dimension at all?"
 
274
        oth_dim_items = util.getTreeCtrlDescendants(oth_dim_tree, 
 
275
                                                    oth_dim_root)
 
276
        oth_dim_labels = [oth_dim_tree.GetItemText(x) for \
 
277
                                  x in oth_dim_items]
 
278
        return text in oth_dim_labels
 
279
    
 
280
    def UsedInThisDim(self, text, dim_tree, dim_root):
 
281
        "Is this variable already used in this dimension?"
 
282
        dim_items = util.getTreeCtrlDescendants(dim_tree, 
 
283
                                                dim_root)
 
284
        dim_labels = [dim_tree.GetItemText(x) for x in dim_items]
 
285
        return text in dim_labels
 
286
                
 
287
    def OnRowDelete(self, event):
 
288
        """
 
289
        Delete row var and all its children.
 
290
        If it has a parent, set its measures to the default list.
 
291
        If colmeasures is set, delete that too.
 
292
        """
 
293
        selected_ids = self.rowtree.GetSelections()
 
294
        if len(selected_ids) == 0:
 
295
            return
 
296
        first_selected_id = selected_ids[0]
 
297
        parent = self.rowtree.GetItemParent(first_selected_id)
 
298
        if parent:
 
299
            item_conf = self.rowtree.GetItemPyData(parent)
 
300
            if item_conf:
 
301
                item_conf.measures_lst = [self.demo_tab.default_measure]
 
302
        for selected_id in selected_ids:
 
303
            self.rowtree.DeleteChildren(selected_id)
 
304
        if self.rowRoot not in selected_ids:
 
305
            for selected_id in selected_ids:
 
306
                self.rowtree.Delete(selected_id)
 
307
        # if the rowtree is now empty, ensure 
 
308
        # colmeasures is wiped as well (and restore Add and Add Under 
 
309
        # buttons too ;-)
 
310
        if self.tab_type == make_table.COL_MEASURES and \
 
311
                not util.ItemHasChildren(tree=self.rowtree,
 
312
                                     parent=self.rowRoot) and \
 
313
                self.col_no_vars_item:
 
314
            self.coltree.DeleteChildren(self.colRoot)
 
315
            self.btnColAdd.Enable()
 
316
            self.btnColAddUnder.Enable()
 
317
            self.col_no_vars_item = None #it will be reallocated
 
318
        self.UpdateDemoDisplay()
 
319
            
 
320
    def OnColDelete(self, event):
 
321
        "Delete col var and all its children"
 
322
        selected_ids = self.coltree.GetSelections()
 
323
        if len(selected_ids) == 0:
 
324
            return
 
325
        first_selected_id = selected_ids[0]
 
326
        parent = self.coltree.GetItemParent(first_selected_id)
 
327
        if parent:
 
328
            item_conf = self.coltree.GetItemPyData(parent)
 
329
            if item_conf:
 
330
                item_conf.measures_lst = [self.demo_tab.default_measure]
 
331
        for selected_id in selected_ids:
 
332
            self.coltree.DeleteChildren(selected_id)
 
333
        if self.colRoot not in selected_ids:
 
334
            for selected_id in selected_ids:
 
335
                self.coltree.Delete(selected_id)
 
336
            self.UpdateDemoDisplay()
 
337
        if self.col_no_vars_item in selected_ids:
 
338
            self.btnColAdd.Enable()
 
339
            self.btnColAddUnder.Enable()
 
340
            self.col_no_vars_item = None #it will be reallocated
 
341
            
 
342
    def OnRowConfig(self, event):
 
343
        "Configure row button clicked."
 
344
        self.ConfigRow()
 
345
    
 
346
    def ConfigRow(self):
 
347
        """
 
348
        Configure row item e.g. measures, total.
 
349
        If a Summary Table, rows are never nested i.e. always terminal.
 
350
        Rows have no sorting options if a row summary table.
 
351
        Terminal nodes can have either label or freq sorting and
 
352
            other nodes can only have label sorting.
 
353
        """
 
354
        if not util.ItemHasChildren(self.rowtree, self.rowRoot):
 
355
            return
 
356
        selected_ids = self.rowtree.GetSelections()
 
357
        first_selected_id = selected_ids[0] 
 
358
        # get results from appropriate dialog and store as data
 
359
        inc_measures = (self.tab_type == make_table.ROW_SUMM)
 
360
        if self.tab_type == make_table.ROW_SUMM:
 
361
            sort_opt_allowed = SORT_OPT_NONE
 
362
        elif not util.ItemHasChildren(tree=self.rowtree, 
 
363
                                      parent=first_selected_id):
 
364
            sort_opt_allowed = SORT_OPT_ALL
 
365
        else:
 
366
            sort_opt_allowed = SORT_OPT_BY_LABEL
 
367
        dlg = DlgRowConfig(parent=self, var_labels=self.var_labels,
 
368
                           node_ids=selected_ids, tree=self.rowtree, 
 
369
                           inc_measures=inc_measures,
 
370
                           sort_opt_allowed=sort_opt_allowed)
 
371
        dlg.ShowModal()
 
372
        self.UpdateDemoDisplay()
 
373
    
 
374
    def OnColConfig(self, event):
 
375
        "Configure column button clicked."
 
376
        self.ConfigCol()
 
377
 
 
378
    def ConfigCol(self):
 
379
        """
 
380
        Configure column item e.g. measures, total.
 
381
        Either with columns vars or without.  If without, only filtering 
 
382
            by row vars.  Total doesn't make sense in this context.
 
383
        """
 
384
        empty_tree = not util.ItemHasChildren(tree=self.coltree, 
 
385
                                              parent=self.colRoot)
 
386
        # empty_tree = not self.coltree.ItemHasChildren(self.colRoot) #buggy if root hidden
 
387
        # i.e. if there is only the root there
 
388
        # no col vars - just set measures (without total)
 
389
        if empty_tree and self.tab_type == make_table.COL_MEASURES:
 
390
            #add special node before getting config
 
391
            self.col_no_vars_item = \
 
392
                self.coltree.AppendItem(self.colRoot, 
 
393
                                        make_table.COL_MEASURES_TREE_LBL)
 
394
            self.demo_tab.setInitialConfig(dimtables.COLDIM, 
 
395
                                           self.col_no_vars_item)
 
396
            self.demo_tab.col_no_vars_item = self.col_no_vars_item
 
397
            self.coltree.ExpandAll(self.colRoot)
 
398
            self.coltree.SelectItem(self.col_no_vars_item)
 
399
            self.btnColAdd.Disable()
 
400
            self.btnColAddUnder.Disable()
 
401
            self.getColConfig(node_ids=[self.col_no_vars_item], 
 
402
                                  has_col_vars=False)
 
403
            self.UpdateDemoDisplay()
 
404
        elif empty_tree and self.tab_type == make_table.ROW_SUMM:
 
405
            return
 
406
        else: # not an empty col_measures or row summ table
 
407
            selected_ids = self.coltree.GetSelections()
 
408
            # the ids must all have the same parental status
 
409
            # if one has children, they all must
 
410
            # if one has no children, none can
 
411
            config_ok = True
 
412
            if not empty_tree:
 
413
                first_has_children = util.ItemHasChildren(tree=self.coltree,
 
414
                                                          parent=selected_ids[0])
 
415
                for selected_id in selected_ids[1:]:
 
416
                    if util.ItemHasChildren(tree=self.coltree,
 
417
                                            parent=selected_id) != first_has_children:
 
418
                        config_ok = False
 
419
                        break
 
420
            if config_ok:
 
421
                if self.col_no_vars_item in selected_ids:
 
422
                    self.getColConfig(node_ids=[self.col_no_vars_item], 
 
423
                                      has_col_vars=False)
 
424
                elif self.colRoot not in selected_ids:
 
425
                    self.getColConfig(node_ids=selected_ids, has_col_vars=True)
 
426
                self.UpdateDemoDisplay()
 
427
            else:
 
428
                wx.MessageBox("If configuring multiple items at once, they must " + \
 
429
                              "all have children or none can have children")
 
430
            
 
431
    def getColConfig(self, node_ids, has_col_vars):
 
432
        """
 
433
        Get results from appropriate dialog and store as data.
 
434
        Only ask for measures if a table with colmeasures and
 
435
            the node is terminal.
 
436
        If the column item is col_no_vars_item then no sorting options.
 
437
        If a row summary table, no sorting options.
 
438
        Terminal nodes can have either label or freq sorting and
 
439
            other nodes can only have label sorting.
 
440
        """
 
441
        # include measures if the selected items have no children
 
442
        # only need to test one because they are all requried to be the same
 
443
        has_children = True
 
444
        if not node_ids:
 
445
            has_children = False
 
446
        else:
 
447
            item, cookie = self.coltree.GetFirstChild(node_ids[0])
 
448
            has_children = True if item else False
 
449
        inc_measures = ((self.tab_type == make_table.COL_MEASURES)
 
450
                        and not has_children)
 
451
        if self.col_no_vars_item in node_ids \
 
452
                or self.tab_type != make_table.COL_MEASURES:
 
453
            sort_opt_allowed = SORT_OPT_NONE
 
454
        elif not util.ItemHasChildren(tree=self.coltree, 
 
455
                                      parent=node_ids[0]):
 
456
            sort_opt_allowed = SORT_OPT_ALL
 
457
        else:
 
458
            sort_opt_allowed = SORT_OPT_BY_LABEL
 
459
        dlg = DlgColConfig(parent=self, var_labels=self.var_labels,
 
460
                           node_ids=node_ids, tree=self.coltree, 
 
461
                           inc_measures=inc_measures, 
 
462
                           sort_opt_allowed=sort_opt_allowed, 
 
463
                           has_col_vars=has_col_vars)
 
464
        dlg.ShowModal()
 
465
 
 
466
    def OnAddRowEnterWindow(self, event):
 
467
        "Hover over Add (for Row) button"
 
468
        self.statusbar.SetStatusText("Add row to table")
 
469
            
 
470
    def OnAddColEnterWindow(self, event):
 
471
        "Hover over Add (for Column) button"
 
472
        self.statusbar.SetStatusText("Add column to table")
 
473
        
 
474
    def OnAddRowUnderEnterWindow(self, event):
 
475
        "Hover over Add Under (for Row) button"
 
476
        self.statusbar.SetStatusText("Nest row under existing table row")
 
477
 
 
478
    def OnAddColUnderEnterWindow(self, event):
 
479
        "Hover over Add Under (for Column) button"
 
480
        self.statusbar.SetStatusText("Nest column under existing " + \
 
481
                                     "table column")
 
482
        
 
483
    def OnDeleteRowEnterWindow(self, event):
 
484
        "Hover over Delete (for Row) button"
 
485
        self.statusbar.SetStatusText("Delete table row and all rows " + \
 
486
                                     "nested underneath")
 
487
        
 
488
    def OnDeleteColEnterWindow(self, event):
 
489
        "Hover over Delete (for Column) button"
 
490
        self.statusbar.SetStatusText("Delete table column and all " + \
 
491
                                     "columns nested underneath")
 
492
    def OnConfigRowEnterWindow(self, event):
 
493
        "Hover over Config (for Row) button"
 
494
        self.statusbar.SetStatusText("Configure row variable - " + \
 
495
                                     "e.g. measures, totals")
 
496
 
 
497
    def OnConfigColEnterWindow(self, event):
 
498
        "Hover over Config (for column) button"
 
499
        self.statusbar.SetStatusText("Configure column variable - " + \
 
500
                                     "e.g. measures, totals")
 
501
        
 
502
    def setupDimTree(self, tree):
 
503
        "Setup Dim Tree and return root"
 
504
        tree.AddColumn("Variable")
 
505
        tree.AddColumn("Config")
 
506
        tree.SetMainColumn(0)
 
507
        tree.SetColumnWidth(0, 150)
 
508
        tree.SetColumnWidth(1, 500)
 
509
        #MinSize lets SetSizeHints make a more sensible guess for starting point
 
510
        tree.SetMinSize((70, 110))
 
511
        return tree.AddRoot("root")
 
512
    
 
513
    def EnableRowSel(self, enable=True):
 
514
        "Enable (or disable) all row selection objects"
 
515
        self.btnRowAdd.Enable(enable)
 
516
        self.btnRowAddUnder.Enable(enable)
 
517
        self.btnRowDel.Enable(enable)
 
518
        self.btnRowConf.Enable(enable)
 
519
        self.rowtree.Enable(enable)
 
520
        
 
521
    def EnableColButtons(self, enable=True):
 
522
        "Enable (or disable) col buttons"
 
523
        self.btnColAdd.Enable(enable)
 
524
        self.btnColAddUnder.Enable(enable)
 
525
        self.btnColDel.Enable(enable)
 
526
        self.btnColConf.Enable(enable) 
 
527
        
 
528
    
 
529
class GetSettings(table_entry.TableEntryDlg):
 
530
    
 
531
    def __init__(self, title, var_desc, data, new_grid_data, val_type):
 
532
        """
 
533
        data - list of tuples (must have at least one item, even if only a 
 
534
            "rename me".
 
535
        col_dets - [(col_type, col_label, col_width), ( , ) ...]
 
536
        new_grid_data - add details to it in form of a list of tuples.
 
537
        """
 
538
        col_dets = [("Value", val_type, 50), 
 
539
                    ("Label", table_entry.COL_STR, 200)]
 
540
        grid_size = (250, 250)
 
541
        wx.Dialog.__init__(self, None, title=title,
 
542
                          size=(400,400), 
 
543
                          style=wx.RESIZE_BORDER|wx.CAPTION|wx.CLOSE_BOX|
 
544
                              wx.SYSTEM_MENU)
 
545
        self.panel = wx.Panel(self)
 
546
        self.var_desc = var_desc
 
547
        # New controls
 
548
        lblVarLabel = wx.StaticText(self.panel, -1, "Variable Label:")
 
549
        lblVarLabel.SetFont(font=wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD))
 
550
        lblVarNotes = wx.StaticText(self.panel, -1, "Notes:")
 
551
        lblVarNotes.SetFont(font=wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD))
 
552
        self.txtVarLabel = wx.TextCtrl(self.panel, -1, self.var_desc[0], 
 
553
                                       size=(250,-1))
 
554
        self.txtVarNotes = wx.TextCtrl(self.panel, -1, self.var_desc[1], 
 
555
                                       size=(50,40), style=wx.TE_MULTILINE)        
 
556
        # sizers
 
557
        self.szrMain = wx.BoxSizer(wx.VERTICAL)
 
558
        self.szrVarLabel = wx.BoxSizer(wx.HORIZONTAL)
 
559
        self.szrVarLabel.Add(lblVarLabel, 0, wx.RIGHT, 5)
 
560
        self.szrVarLabel.Add(self.txtVarLabel, 1, wx.GROW)
 
561
        self.szrVarNotes = wx.BoxSizer(wx.HORIZONTAL)
 
562
        self.szrVarNotes.Add(lblVarNotes, 0, wx.GROW|wx.RIGHT, 5)
 
563
        self.szrVarNotes.Add(self.txtVarNotes, 1, wx.GROW)
 
564
        self.szrMain.Add(self.szrVarLabel, 0, wx.ALL, 10)
 
565
        self.szrMain.Add(self.szrVarNotes, 1, 
 
566
                         wx.GROW|wx.LEFT|wx.RIGHT|wx.BOTTOM, 10)
 
567
        self.tabentry = table_entry.TableEntry(self, self.panel, 
 
568
                                               self.szrMain, False, 
 
569
                                               grid_size, col_dets, data,  
 
570
                                               new_grid_data)
 
571
        self.SetupButtons()
 
572
        self.szrMain.Add(self.szrButtons, 0, wx.ALL, 10)
 
573
        self.panel.SetSizer(self.szrMain)
 
574
        self.szrMain.SetSizeHints(self)
 
575
        self.Layout()
 
576
        self.tabentry.grid.SetFocus()
 
577
 
 
578
    def OnOK(self, event):
 
579
        "Override so we can extend to include var label and notes"
 
580
        self.var_desc.pop()
 
581
        self.var_desc.pop() # emptied but same list
 
582
        self.var_desc.append(self.txtVarLabel.GetValue())
 
583
        self.var_desc.append(self.txtVarNotes.GetValue())
 
584
        self.tabentry.UpdateNewGridData()
 
585
        self.Destroy()
 
586
        self.SetReturnCode(wx.ID_OK)
 
587
 
 
588
 
 
589
class DlgConfig(wx.Dialog):
 
590
    
 
591
    def __init__(self, parent, var_labels, node_ids, tree, title, size, 
 
592
                 allow_tot, sort_opt_allowed):
 
593
        """
 
594
        Parent class for all dialogs collecting configuration details 
 
595
            for rows and cols.
 
596
        node_ids - list, even if only one item selected.
 
597
        """
 
598
        wx.Dialog.__init__(self, parent, id=-1, title=title, 
 
599
                           size=size)
 
600
        self.tree = tree
 
601
        self.allow_tot = allow_tot
 
602
        self.sort_opt_allowed = sort_opt_allowed
 
603
        self.node_ids = node_ids
 
604
        first_node_id = node_ids[0]
 
605
        # base item configuration on first one selected
 
606
        item_conf = self.tree.GetItemPyData(first_node_id)
 
607
        chkSize = (150, 20)
 
608
        szrMain = wx.BoxSizer(wx.VERTICAL)
 
609
        lblVar = wx.StaticText(self, -1, tree.GetItemText(first_node_id))
 
610
        szrMain.Add(lblVar, 0, wx.GROW|wx.TOP|wx.LEFT|wx.RIGHT, 10)
 
611
        if self.allow_tot:
 
612
            boxMisc = wx.StaticBox(self, -1, "Misc")
 
613
            szrMisc = wx.StaticBoxSizer(boxMisc, wx.VERTICAL)
 
614
            self.chkTotal = wx.CheckBox(self, -1, make_table.HAS_TOTAL, 
 
615
                                        size=chkSize)
 
616
            if item_conf.has_tot:
 
617
                self.chkTotal.SetValue(True)
 
618
            szrMisc.Add(self.chkTotal, 0, wx.LEFT, 5)
 
619
            szrMain.Add(szrMisc, 0, wx.GROW|wx.ALL, 10)
 
620
        if self.sort_opt_allowed != SORT_OPT_NONE:
 
621
            self.radSortOpts = wx.RadioBox(self, -1, "Sort order",
 
622
                                       choices=[dimtables.SORT_NONE, 
 
623
                                                dimtables.SORT_LABEL,
 
624
                                                dimtables.SORT_FREQ_ASC,
 
625
                                                dimtables.SORT_FREQ_DESC],
 
626
                                       size=(400,50))
 
627
            # set selection according to existing item_conf
 
628
            if item_conf.sort_order == dimtables.SORT_NONE:
 
629
                self.radSortOpts.SetSelection(0)
 
630
            elif item_conf.sort_order == dimtables.SORT_LABEL:
 
631
                self.radSortOpts.SetSelection(1)
 
632
            elif item_conf.sort_order == dimtables.SORT_FREQ_ASC:
 
633
                self.radSortOpts.SetSelection(2)
 
634
            elif item_conf.sort_order == dimtables.SORT_FREQ_DESC:
 
635
                self.radSortOpts.SetSelection(3)
 
636
            if self.sort_opt_allowed == SORT_OPT_BY_LABEL:
 
637
                # disable freq options
 
638
                self.radSortOpts.EnableItem(2, False)
 
639
                self.radSortOpts.EnableItem(3, False)
 
640
 
 
641
            szrMain.Add(self.radSortOpts, 0, wx.GROW|wx.LEFT|wx.RIGHT, 10)
 
642
        self.measure_chks_dic = {}
 
643
        if self.measures:
 
644
            boxMeasures = wx.StaticBox(self, -1, "Measures")
 
645
            szrMeasures = wx.StaticBoxSizer(boxMeasures, wx.VERTICAL)
 
646
            for measure, label in self.measures:
 
647
                chk = wx.CheckBox(self, -1, label, 
 
648
                            size=chkSize)
 
649
                if measure in item_conf.measures_lst:
 
650
                    chk.SetValue(True)
 
651
                self.measure_chks_dic[measure] = chk
 
652
                szrMeasures.Add(chk, 1, wx.ALL, 5)
 
653
            szrMain.Add(szrMeasures, 1, wx.GROW|wx.ALL, 10)
 
654
        btnCancel = wx.Button(self, wx.ID_CANCEL)
 
655
        btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)            
 
656
        btnOK = wx.Button(self, wx.ID_OK) # must have ID of wx.ID_OK 
 
657
        # to trigger validators (no event binding needed) and 
 
658
        # for std dialog button layout
 
659
        btnOK.Bind(wx.EVT_BUTTON, self.OnOK)
 
660
        btnOK.SetDefault()
 
661
        # using the approach which will follow the platform convention 
 
662
        # for standard buttons
 
663
        szrButtons = wx.StdDialogButtonSizer()
 
664
        szrButtons.AddButton(btnCancel)
 
665
        szrButtons.AddButton(btnOK)
 
666
        szrButtons.Realize()
 
667
        szrMain.Add(szrButtons, 0, wx.ALL, 10)
 
668
        szrMain.SetSizeHints(self)
 
669
        self.SetSizer(szrMain)
 
670
        self.Fit()
 
671
             
 
672
    def OnOK(self, event):
 
673
        "Store selection details into item conf object"
 
674
        # measures
 
675
        measures_lst = []
 
676
        any_measures = False
 
677
        for measure, label in self.measures:
 
678
            ticked = self.measure_chks_dic[measure].GetValue()
 
679
            if ticked:
 
680
                any_measures = True
 
681
                measures_lst.append(measure)
 
682
        if not any_measures and self.min_measure:
 
683
            measures_lst.append(self.min_measure)
 
684
        # tot
 
685
        has_tot = self.allow_tot and self.chkTotal.GetValue()
 
686
        # sort order
 
687
        if self.sort_opt_allowed == SORT_OPT_NONE:
 
688
            sort_order = dimtables.SORT_NONE
 
689
        else:
 
690
            sort_opt_selection = self.radSortOpts.GetSelection()
 
691
            if sort_opt_selection == 0:
 
692
                sort_order = dimtables.SORT_NONE
 
693
            if sort_opt_selection == 1:
 
694
                sort_order = dimtables.SORT_LABEL
 
695
            if sort_opt_selection == 2:
 
696
                sort_order = dimtables.SORT_FREQ_ASC
 
697
            if sort_opt_selection == 3:
 
698
                sort_order = dimtables.SORT_FREQ_DESC
 
699
        for node_id in self.node_ids:
 
700
            bolnumeric = self.tree.GetItemPyData(node_id).bolnumeric
 
701
            item_conf = make_table.ItemConfig(measures_lst, has_tot, 
 
702
                                              sort_order, bolnumeric)
 
703
            self.tree.SetItemPyData(node_id, item_conf)        
 
704
            self.tree.SetItemText(node_id, item_conf.getSummary(), 1)
 
705
        self.Destroy()
 
706
        self.SetReturnCode(wx.ID_OK) # or nothing happens!  
 
707
        # Prebuilt dialogs must do this internally.
 
708
    
 
709
    def OnCancel(self, event):
 
710
        "Cancel adding new package"
 
711
        self.Destroy()
 
712
        self.SetReturnCode(wx.ID_CANCEL)
 
713
    
 
714
    
 
715
class DlgRowConfig(DlgConfig):
 
716
    
 
717
    def __init__(self, parent, var_labels, node_ids, tree, inc_measures, 
 
718
                 sort_opt_allowed):
 
719
        title = "Configure Row Item"
 
720
        if inc_measures:
 
721
            self.measures = [
 
722
                (dimtables.MEAN, 
 
723
                    dimtables.measures_long_label_dic[dimtables.MEAN]), 
 
724
                (dimtables.MEDIAN, 
 
725
                    dimtables.measures_long_label_dic[dimtables.MEDIAN]), 
 
726
                (dimtables.SUMM_N, 
 
727
                    dimtables.measures_long_label_dic[dimtables.SUMM_N]), 
 
728
                (dimtables.STD_DEV, 
 
729
                    dimtables.measures_long_label_dic[dimtables.STD_DEV]),
 
730
                (dimtables.SUM, 
 
731
                    dimtables.measures_long_label_dic[dimtables.SUM]),
 
732
                ]
 
733
            self.min_measure = dimtables.MEAN
 
734
        else:
 
735
            self.measures = []
 
736
            self.min_measure = None
 
737
        size = wx.DefaultSize
 
738
        DlgConfig.__init__(self, parent, var_labels, node_ids, tree, 
 
739
                           title, size, allow_tot=not inc_measures,
 
740
                           sort_opt_allowed=sort_opt_allowed)
 
741
        
 
742
class DlgColConfig(DlgConfig):
 
743
    
 
744
    def __init__(self, parent, var_labels, node_ids, tree, inc_measures, 
 
745
                 sort_opt_allowed, has_col_vars=True):
 
746
        title = "Configure Column Item"
 
747
        if inc_measures:
 
748
            self.measures = [
 
749
                (dimtables.FREQ, 
 
750
                    dimtables.measures_long_label_dic[dimtables.FREQ]), 
 
751
                (dimtables.ROWPCT, 
 
752
                    dimtables.measures_long_label_dic[dimtables.ROWPCT]),
 
753
                (dimtables.COLPCT, 
 
754
                    dimtables.measures_long_label_dic[dimtables.COLPCT])
 
755
                ]
 
756
            self.min_measure = dimtables.FREQ
 
757
        else:
 
758
            self.measures = []
 
759
            self.min_measure = None
 
760
        size = wx.DefaultSize
 
761
        DlgConfig.__init__(self, parent, var_labels, node_ids, tree, 
 
762
                           title, size, allow_tot=has_col_vars, 
 
763
                           sort_opt_allowed=sort_opt_allowed)