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

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 16cd735 was 657e52c, checked in by Jae Cho <jhjcho@…>, 12 years ago

merging from the release 2.2.0

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