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

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 944724a was 944724a, checked in by Jae Cho <jhjcho@…>, 12 years ago

some fixes in cat mod frozen (sizer) dialog:mac

  • Property mode set to 100755
File size: 16.5 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            icon = self.parent.GetIcon()
261            modify_dialog.SetIcon(icon)
262            if modify_dialog.ShowModal() == wx.ID_OK:
263                self.by_model_dict[selected_model] = \
264                    modify_dialog.get_category()
265                self._regenerate_master_dict()
266                self._fill_lists()
267                self._set_enabled()
268
269    def _on_ok(self, event):
270        """
271        Close the manager
272        """
273        self._save_state()
274        evt = ChangeCategoryEvent()
275        wx.PostEvent(self.parent, evt)
276
277        self.Destroy()
278
279    def _on_cancel(self, event):
280        """
281        On cancel
282        """
283        self.Destroy()
284
285    def _save_state(self):
286        """
287        Serializes categorization info to file
288        """
289
290        self._regenerate_master_dict()
291
292        cat_file = open(CategoryInstaller.get_user_file(), 'wb')
293
294        pickle.dump( self.master_category_dict, cat_file )
295
296   
297    def _read_category_info(self):
298        """
299        Read in categorization info from file
300        """
301        try:
302                file = CategoryInstaller.get_user_file()
303                if os.path.isfile(file):
304                    cat_file = open(file, 'rb')
305                    self.master_category_dict = pickle.load(cat_file)
306                else:
307                        cat_file = open(CategoryInstaller.get_default_file(), 'rb')
308                        self.master_category_dict = pickle.load(cat_file)
309        except IOError:
310            print 'Problem reading in category file. Please review'
311
312
313        self._regenerate_model_dict()
314
315    def _get_cat_list(self):
316        """
317        Returns a simple list of categories
318        """
319        cat_list = list()
320        for category in self.master_category_dict.iterkeys():
321            if not category == 'Uncategorized':
322                cat_list.append(category)
323   
324        return cat_list
325
326    def _regenerate_model_dict(self):
327        """
328        regenerates self.by_model_dict which has each model
329        name as the key
330        and the list of categories belonging to that model
331        along with the enabled mapping
332        """
333        self.by_model_dict = defaultdict(list)
334        for category in self.master_category_dict:
335            for (model, enabled) in self.master_category_dict[category]:
336                self.by_model_dict[model].append(category)
337                self.model_enabled_dict[model] = enabled
338
339    def _regenerate_master_dict(self):
340        """
341        regenerates self.master_category_dict from
342        self.by_model_dict and self.model_enabled_dict
343        """
344        self.master_category_dict = defaultdict(list)
345        for model in self.by_model_dict:
346            for category in self.by_model_dict[model]:
347                self.master_category_dict[category].append\
348                    ((model, self.model_enabled_dict[model]))
349   
350
351
352class ChangeCat(wx.Dialog):
353    """
354    dialog for changing the categories of a model
355    """
356
357    def __init__(self, parent, title, cat_list, current_cats):
358        """
359        Actual editor for a certain category
360        :param parent: Window parent
361        :param title: Window title
362        :param cat_list: List of all categories
363        :param current_cats: List of categories applied to current model
364        """
365        wx.Dialog.__init__(self, parent, title = title, size=(480, 420))
366
367        self.current_cats = current_cats
368        if str(self.current_cats[0]) == 'Uncategorized':
369            self.current_cats = []
370           
371        vbox = wx.BoxSizer(wx.VERTICAL)
372        gs = wx.GridSizer(3, 2, 5, 5)
373
374        self.cat_list = cat_list
375       
376        self.cat_text = wx.StaticText(self, label = "Current categories: ")
377        self.current_categories = wx.ListBox(self, 
378                                             choices = self.current_cats
379                                             , size=(300, 100))
380        self.existing_check = wx.RadioButton(self, 
381                                             label = 'Choose Existing')
382        self.new_check = wx.RadioButton(self, label = 'Create new')
383        self.exist_combo = wx.ComboBox(self, style = wx.CB_READONLY, 
384                                       size=(220,-1), choices = cat_list)
385        self.exist_combo.SetSelection(0)
386
387        self.add_sb = wx.StaticBox(self, label = "Add Category")
388        self.add_sb_sizer = wx.StaticBoxSizer(self.add_sb, wx.VERTICAL)
389        self.remove_sb = wx.StaticBox(self, label = "Remove Category")
390        self.remove_sb_sizer = wx.StaticBoxSizer(self.remove_sb, 
391                                                 wx.VERTICAL)
392
393        self.new_text = wx.TextCtrl(self, size=(220, -1))
394        self.ok_button = wx.Button(self, wx.ID_OK, "Done")
395        self.add_button = wx.Button(self, label = "Add")
396        self.add_button.Bind(wx.EVT_BUTTON, self.on_add)
397        self.remove_button = wx.Button(self, label = "Remove Selected")
398        self.remove_button.Bind(wx.EVT_BUTTON, self.on_remove)
399
400        self.existing_check.Bind(wx.EVT_RADIOBUTTON, self.on_existing)
401        self.new_check.Bind(wx.EVT_RADIOBUTTON, self.on_newcat)
402        self.existing_check.SetValue(True)
403
404       
405        vbox.Add(self.cat_text, flag = wx.LEFT | wx.TOP | wx.ALIGN_LEFT, 
406                 border = 10)
407        vbox.Add(self.current_categories, flag = wx.ALL | wx.EXPAND, 
408                 border = 10  )
409
410       
411
412        gs.AddMany( [ (self.existing_check, 5, wx.ALL),
413                      (self.exist_combo, 5, wx.ALL, 5),
414                      (self.new_check, 5, wx.ALL),
415                      (self.new_text, 5, wx.ALL, 5),
416                      ((-1,-1)),
417                      (self.add_button, 5, wx.ALL | wx.ALIGN_RIGHT, 5) ] )
418
419        self.add_sb_sizer.Add(gs, proportion = 1, flag = wx.ALL, border = 5)
420        vbox.Add(self.add_sb_sizer, flag = wx.ALL | wx.EXPAND, border = 10)
421
422        self.remove_sb_sizer.Add(self.remove_button, border = 5, 
423                                 flag = wx.ALL | wx.ALIGN_RIGHT)
424        vbox.Add(self.remove_sb_sizer, 
425                 flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 
426                 border = 10)
427        vbox.Add(self.ok_button, flag = wx.ALL | wx.ALIGN_RIGHT, 
428                 border = 10)
429       
430        if self.current_categories.GetCount() > 0:
431                self.current_categories.SetSelection(0)
432        self.new_text.Disable()
433        self.SetSizer(vbox)
434        self.Centre()
435        self.Show(True)
436
437    def on_add(self, event):
438        """
439        Callback for new category added
440        """
441        new_cat = ''
442        if self.existing_check.GetValue():
443            new_cat = str(self.exist_combo.GetValue())
444        else:
445            new_cat = str(self.new_text.GetValue())
446            if new_cat in self.cat_list:
447                wx.MessageBox('%s is already a model' % new_cat, 'Error',
448                              wx.OK | wx.ICON_EXCLAMATION )
449                return
450
451        if new_cat in self.current_cats:
452            wx.MessageBox('%s is already included in this model' \
453                              % new_cat, 'Error',
454                          wx.OK | wx.ICON_EXCLAMATION )
455            return
456
457        self.current_cats.append(new_cat)
458        self.current_categories.SetItems(self.current_cats)
459           
460       
461    def on_remove(self, event):
462        """
463        Callback for a category removed
464        """
465        if self.current_categories.GetSelection() == wx.NOT_FOUND:
466            wx.MessageBox('Please select a category to remove', 'Error',
467                          wx.OK | wx.ICON_EXCLAMATION )
468        else:
469            self.current_categories.Delete( \
470                self.current_categories.GetSelection())
471            self.current_cats = self.current_categories.GetItems()
472
473       
474
475    def on_newcat(self, event):
476        """
477        Callback for new category added
478        """
479        self.new_text.Enable()
480        self.exist_combo.Disable()
481
482
483    def on_existing(self, event):   
484        """
485        Callback for existing category selected
486        """
487        self.new_text.Disable()
488        self.exist_combo.Enable()
489
490    def get_category(self):
491        """
492        Returns a list of categories applying to this model
493        """
494        if not self.current_cats:
495            self.current_cats.append("Uncategorized")
496
497        ret = list()
498        for cat in self.current_cats:
499            ret.append(str(cat))
500        return ret
501
502if __name__ == '__main__':
503       
504   
505    if(len(sys.argv) > 1):
506        app = wx.App()
507        CategoryManager(None, -1, 'Category Manager', sys.argv[1])
508        app.MainLoop()
509    else:
510        app = wx.App()
511        CategoryManager(None, -1, 'Category Manager', sys.argv[1])
512        app.MainLoop()
513
Note: See TracBrowser for help on using the repository browser.