source: sasview/src/sas/guiframe/CategoryManager.py @ 60dca65c

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 60dca65c was 79492222, checked in by krzywon, 10 years ago

Changed the file and folder names to remove all SANS references.

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