source: sasview/sansguiframe/src/sans/guiframe/CategoryManager.py @ bda809e

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since bda809e was ea5fa58, checked in by Jae Cho <jhjcho@…>, 12 years ago

category stuffs start working in interp. environment

  • Property mode set to 100755
File size: 16.4 KB
Line 
1#!/usr/bin/python
2
3"""
4
5/**
6        This software was developed by Institut Laue-Langevin as part of
7        Distributed Data Analysis of Neutron Scattering Experiments (DANSE).
8
9        Copyright 2012 Institut Laue-Langevin
10
11**/
12
13"""
14
15
16import wx
17import sys
18import os
19from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
20from collections import defaultdict
21import cPickle as pickle
22from sans.guiframe.events import ChangeCategoryEvent
23from sans.guiframe.CategoryInstaller import CategoryInstaller
24
25""" Notes
26The category manager mechanism works from 3 data structures used:
27- self.master_category_dict: keys are the names of categories,
28the values are lists of tuples,
29the first being the model names (the models belonging to that
30category), the second a boolean
31of whether or not the model is enabled
32- self.by_model_dict: keys are model names, values are a list
33of categories belonging to that model
34- self.model_enabled_dict: keys are model names, values are
35bools of whether the model is enabled
36use self._regenerate_model_dict() to create the latter two
37structures from the former
38use self._regenerate_master_dict() to create the first
39structure from the latter two
40
41The need for so many data structures comes from the fact
42sometimes we need fast access
43to all the models in a category (eg user selection from the gui)
44and sometimes we need access to all the categories
45corresponding to a model (eg user modification of model categories)
46
47"""
48
49
50
51class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, 
52                    ListCtrlAutoWidthMixin):
53    """
54    Taken from
55    http://zetcode.com/wxpython/advanced/
56    """
57
58    def __init__(self, parent, callback_func):
59        """
60        Initialization
61        :param parent: Parent window
62        :param callback_func: A function to be called when
63        an element is clicked
64        """
65        wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT \
66                                 | wx.SUNKEN_BORDER)
67        CheckListCtrlMixin.__init__(self)
68        ListCtrlAutoWidthMixin.__init__(self)
69
70        self.callback_func = callback_func
71       
72    def OnCheckItem(self, index, flag):
73        """
74        When the user checks the item we need to save that state
75        """
76        self.callback_func(index, flag)
77   
78
79class CategoryManager(wx.Frame):
80    """
81    A class for managing categories
82    """
83    def __init__(self, parent, win_id, title):
84        """
85        Category Manager Dialog class
86        :param win_id: A new wx ID
87        :param title: Title for the window
88        """
89       
90        # make sure the category file is where it should be
91        self.performance_blocking = False
92
93        self.master_category_dict = defaultdict(list)
94        self.by_model_dict = defaultdict(list)
95        self.model_enabled_dict = defaultdict(bool)
96
97        wx.Frame.__init__(self, parent, win_id, title, size=(650, 400))
98
99        panel = wx.Panel(self, -1)
100        self.parent = parent
101
102        self._read_category_info()
103
104
105        vbox = wx.BoxSizer(wx.VERTICAL)
106        hbox = wx.BoxSizer(wx.HORIZONTAL)
107
108        left_panel = wx.Panel(panel, -1)
109        right_panel = wx.Panel(panel, -1)
110
111        self.cat_list = CheckListCtrl(right_panel, self._on_check)
112        self.cat_list.InsertColumn(0, 'Model', width = 280)
113        self.cat_list.InsertColumn(1, 'Category', width = 240)
114
115        self._fill_lists() 
116        self._regenerate_model_dict()
117        self._set_enabled()     
118
119        vbox2 = wx.BoxSizer(wx.VERTICAL)
120
121        sel = wx.Button(left_panel, -1, 'Enable All', size=(100, -1))
122        des = wx.Button(left_panel, -1, 'Disable All', size=(100, -1))
123        modify_button = wx.Button(left_panel, -1, 'Modify', 
124                                  size=(100, -1))
125        ok_button = wx.Button(left_panel, -1, 'OK', size=(100, -1))
126        cancel_button = wx.Button(left_panel, -1, 'Cancel', 
127                                  size=(100, -1))       
128
129       
130
131        self.Bind(wx.EVT_BUTTON, self._on_selectall, 
132                  id=sel.GetId())
133        self.Bind(wx.EVT_BUTTON, self._on_deselectall, 
134                  id=des.GetId())
135        self.Bind(wx.EVT_BUTTON, self._on_apply, 
136                  id = modify_button.GetId())
137        self.Bind(wx.EVT_BUTTON, self._on_ok, 
138                  id = ok_button.GetId())
139        self.Bind(wx.EVT_BUTTON, self._on_cancel, 
140                  id = cancel_button.GetId())
141
142        vbox2.Add(modify_button, 0, wx.TOP, 10)
143        vbox2.Add((-1, 20))
144        vbox2.Add(sel)
145        vbox2.Add(des)
146        vbox2.Add((-1, 20))
147        vbox2.Add(ok_button)
148        vbox2.Add(cancel_button)
149
150        left_panel.SetSizer(vbox2)
151
152        vbox.Add(self.cat_list, 1, wx.EXPAND | wx.TOP, 3)
153        vbox.Add((-1, 10))
154
155
156        right_panel.SetSizer(vbox)
157
158        hbox.Add(left_panel, 0, wx.EXPAND | wx.RIGHT, 5)
159        hbox.Add(right_panel, 1, wx.EXPAND)
160        hbox.Add((3, -1))
161
162        panel.SetSizer(hbox)
163        self.performance_blocking = True
164
165
166        self.Centre()
167        self.Show(True)
168
169        # gui stuff finished
170
171    def _on_check(self, index, flag):
172        """
173        When the user checks an item we need to immediately save that state.
174        :param index: The index of the checked item
175        :param flag: True or False whether the item was checked
176        """
177        if self.performance_blocking:
178            # for computational reasons we don't want to
179            # call this function every time the gui is set up
180            model_name = self.cat_list.GetItem(index, 0).GetText()
181            self.model_enabled_dict[model_name] = flag
182            self._regenerate_master_dict()
183
184
185    def _fill_lists(self):
186        """
187        Expands lists on the GUI
188        """
189        self.cat_list.DeleteAllItems()
190        model_name_list = [model for model in self.by_model_dict]
191        model_name_list.sort()
192
193        for model in model_name_list:
194            index = self.cat_list.InsertStringItem(sys.maxint, model)
195            self.cat_list.SetStringItem(index, 1, \
196                                            str(self.by_model_dict[model]).\
197                                            replace("'","").\
198                                            replace("[","").\
199                                            replace("]",""))
200
201
202           
203    def _set_enabled(self):
204        """
205        Updates enabled models from self.model_enabled_dict
206        """
207        num = self.cat_list.GetItemCount()
208        for i in range(num):
209            model_name = self.cat_list.GetItem(i, 0).GetText()
210            self.cat_list.CheckItem(i, 
211                                    self.model_enabled_dict[model_name] )
212                                   
213
214
215    def _on_selectall(self, event):
216        """
217        Callback for 'enable all'
218        """
219        self.performance_blocking = False
220        num = self.cat_list.GetItemCount()
221        for i in range(num):
222            self.cat_list.CheckItem(i)
223        for model in self.model_enabled_dict:
224            self.model_enabled_dict[model] = True
225        self._regenerate_master_dict()
226        self.performance_blocking = True
227
228    def _on_deselectall(self, event):
229        """
230        Callback for 'disable all'
231        """
232        self.performance_blocking = False
233        num = self.cat_list.GetItemCount()
234        for i in range(num):
235            self.cat_list.CheckItem(i, False)
236        for model in self.model_enabled_dict:
237            self.model_enabled_dict[model] = False
238        self._regenerate_master_dict()
239        self.performance_blocking = True
240
241    def _on_apply(self, event):
242        """
243        Call up the 'ChangeCat' dialog for category editing
244        """
245
246        if self.cat_list.GetSelectedItemCount() == 0:
247            wx.MessageBox('Please select a model', 'Error',
248                          wx.OK | wx.ICON_EXCLAMATION )
249
250        else:
251            selected_model = \
252                self.cat_list.GetItem(\
253                self.cat_list.GetFirstSelected(), 0).GetText()
254
255
256            modify_dialog = ChangeCat(self, 'Change Category: ' + \
257                                          selected_model, 
258                                      self._get_cat_list(),
259                                      self.by_model_dict[selected_model])
260
261            if modify_dialog.ShowModal() == wx.ID_OK:
262                self.by_model_dict[selected_model] = \
263                    modify_dialog.get_category()
264                self._regenerate_master_dict()
265                self._fill_lists()
266                self._set_enabled()
267
268    def _on_ok(self, event):
269        """
270        Close the manager
271        """
272        self._save_state()
273        evt = ChangeCategoryEvent()
274        wx.PostEvent(self.parent, evt)
275
276        self.Destroy()
277
278    def _on_cancel(self, event):
279        """
280        On cancel
281        """
282        self.Destroy()
283
284    def _save_state(self):
285        """
286        Serializes categorization info to file
287        """
288
289        self._regenerate_master_dict()
290
291        cat_file = open(CategoryInstaller.get_user_file(), 'wb')
292
293        pickle.dump( self.master_category_dict, cat_file )
294
295   
296    def _read_category_info(self):
297        """
298        Read in categorization info from file
299        """
300        try:
301                file = CategoryInstaller.get_user_file()
302                if os.path.isfile(file):
303                    cat_file = open(file, 'rb')
304                    self.master_category_dict = pickle.load(cat_file)
305                else:
306                        cat_file = open(CategoryInstaller.get_default_file(), 'rb')
307                        self.master_category_dict = pickle.load(cat_file)
308        except IOError:
309            print 'Problem reading in category file. Please review'
310
311
312        self._regenerate_model_dict()
313
314    def _get_cat_list(self):
315        """
316        Returns a simple list of categories
317        """
318        cat_list = list()
319        for category in self.master_category_dict.iterkeys():
320            if not category == 'Uncategorized':
321                cat_list.append(category)
322   
323        return cat_list
324
325    def _regenerate_model_dict(self):
326        """
327        regenerates self.by_model_dict which has each model
328        name as the key
329        and the list of categories belonging to that model
330        along with the enabled mapping
331        """
332        self.by_model_dict = defaultdict(list)
333        for category in self.master_category_dict:
334            for (model, enabled) in self.master_category_dict[category]:
335                self.by_model_dict[model].append(category)
336                self.model_enabled_dict[model] = enabled
337
338    def _regenerate_master_dict(self):
339        """
340        regenerates self.master_category_dict from
341        self.by_model_dict and self.model_enabled_dict
342        """
343        self.master_category_dict = defaultdict(list)
344        for model in self.by_model_dict:
345            for category in self.by_model_dict[model]:
346                self.master_category_dict[category].append\
347                    ((model, self.model_enabled_dict[model]))
348   
349
350
351class ChangeCat(wx.Dialog):
352    """
353    dialog for changing the categories of a model
354    """
355
356    def __init__(self, parent, title, cat_list, current_cats):
357        """
358        Actual editor for a certain category
359        :param parent: Window parent
360        :param title: Window title
361        :param cat_list: List of all categories
362        :param current_cats: List of categories applied to current model
363        """
364        wx.Dialog.__init__(self, parent, title = title, size=(480, 420))
365
366        self.current_cats = current_cats
367        if str(self.current_cats[0]) == 'Uncategorized':
368            self.current_cats = []
369
370        self.cat_list = cat_list
371       
372        self.cat_text = wx.StaticText(self, label = "Current categories: ")
373        self.current_categories = wx.ListBox(self, 
374                                             choices = self.current_cats
375                                             , size=(300, 100))
376        self.existing_check = wx.RadioButton(self, 
377                                             label = 'Choose Existing')
378        self.new_check = wx.RadioButton(self, label = 'Create new')
379        self.exist_combo = wx.ComboBox(self, style = wx.CB_READONLY, 
380                                       size=(220,-1), choices = cat_list)
381        self.exist_combo.SetSelection(0)
382
383        self.add_sb = wx.StaticBox(self, label = "Add Category")
384        self.add_sb_sizer = wx.StaticBoxSizer(self.add_sb, wx.VERTICAL)
385        self.remove_sb = wx.StaticBox(self, label = "Remove Category")
386        self.remove_sb_sizer = wx.StaticBoxSizer(self.remove_sb, 
387                                                 wx.VERTICAL)
388
389        self.new_text = wx.TextCtrl(self, size=(220, -1))
390        self.ok_button = wx.Button(self, wx.ID_OK, "Done")
391        self.add_button = wx.Button(self, label = "Add")
392        self.add_button.Bind(wx.EVT_BUTTON, self.on_add)
393        self.remove_button = wx.Button(self, label = "Remove Selected")
394        self.remove_button.Bind(wx.EVT_BUTTON, self.on_remove)
395
396        self.existing_check.Bind(wx.EVT_RADIOBUTTON, self.on_existing)
397        self.new_check.Bind(wx.EVT_RADIOBUTTON, self.on_newcat)
398
399
400
401        vbox = wx.BoxSizer(wx.VERTICAL)
402        vbox.Add(self.cat_text, flag = wx.LEFT | wx.TOP | wx.ALIGN_LEFT, 
403                 border = 10)
404        vbox.Add(self.current_categories, flag = wx.ALL | wx.EXPAND, 
405                 border = 10  )
406
407        gs = wx.GridSizer(3, 2, 5, 5)
408
409        gs.AddMany( [ (self.existing_check, 5, wx.ALL),
410                      (self.exist_combo, 5, wx.ALL),
411                      (self.new_check, 5, wx.ALL),
412                      (self.new_text, 5, wx.ALL ),
413                      ((-1,-1)),
414                      (self.add_button, 5, wx.ALL | wx.ALIGN_RIGHT) ] )
415
416        self.add_sb_sizer.Add(gs, proportion = 1, flag = wx.ALL, border = 5)
417        vbox.Add(self.add_sb_sizer, flag = wx.ALL | wx.EXPAND, border = 10)
418
419        self.remove_sb_sizer.Add(self.remove_button, border = 5, 
420                                 flag = wx.ALL | wx.ALIGN_RIGHT)
421        vbox.Add(self.remove_sb_sizer, 
422                 flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 
423                 border = 10)
424        vbox.Add(self.ok_button, flag = wx.ALL | wx.ALIGN_RIGHT, 
425                 border = 10)
426       
427        if self.current_categories.GetCount() > 0:
428                self.current_categories.SetSelection(0)
429        self.new_text.Disable()
430        self.SetSizer(vbox)
431        self.Centre()
432        self.Show(True)
433
434    def on_add(self, event):
435        """
436        Callback for new category added
437        """
438        new_cat = ''
439        if self.existing_check.GetValue():
440            new_cat = str(self.exist_combo.GetValue())
441        else:
442            new_cat = str(self.new_text.GetValue())
443            if new_cat in self.cat_list:
444                wx.MessageBox('%s is already a model' % new_cat, 'Error',
445                              wx.OK | wx.ICON_EXCLAMATION )
446                return
447
448        if new_cat in self.current_cats:
449            wx.MessageBox('%s is already included in this model' \
450                              % new_cat, 'Error',
451                          wx.OK | wx.ICON_EXCLAMATION )
452            return
453
454        self.current_cats.append(new_cat)
455        self.current_categories.SetItems(self.current_cats)
456           
457       
458    def on_remove(self, event):
459        """
460        Callback for a category removed
461        """
462        if self.current_categories.GetSelection() == wx.NOT_FOUND:
463            wx.MessageBox('Please select a category to remove', 'Error',
464                          wx.OK | wx.ICON_EXCLAMATION )
465        else:
466            self.current_categories.Delete( \
467                self.current_categories.GetSelection())
468            self.current_cats = self.current_categories.GetItems()
469
470       
471
472    def on_newcat(self, event):
473        """
474        Callback for new category added
475        """
476        self.new_text.Enable()
477        self.exist_combo.Disable()
478
479
480    def on_existing(self, event):   
481        """
482        Callback for existing category selected
483        """
484        self.new_text.Disable()
485        self.exist_combo.Enable()
486
487    def get_category(self):
488        """
489        Returns a list of categories applying to this model
490        """
491        if not self.current_cats:
492            self.current_cats.append("Uncategorized")
493
494        ret = list()
495        for cat in self.current_cats:
496            ret.append(str(cat))
497        return ret
498
499if __name__ == '__main__':
500       
501   
502    if(len(sys.argv) > 1):
503        app = wx.App()
504        CategoryManager(None, -1, 'Category Manager', sys.argv[1])
505        app.MainLoop()
506    else:
507        app = wx.App()
508        CategoryManager(None, -1, 'Category Manager', sys.argv[1])
509        app.MainLoop()
510
Note: See TracBrowser for help on using the repository browser.