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

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 df7a7e3 was df7a7e3, checked in by Mathieu Doucet <doucetm@…>, 12 years ago

merging category branch

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