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

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

This fixes the frozen category dialog sizers on 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=(485, 425))
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        self.add_sb = wx.StaticBox(self, label = "Add Category")
373        self.add_sb_sizer = wx.StaticBoxSizer(self.add_sb, wx.VERTICAL)
374        gs = wx.GridSizer(3, 2, 5, 5)
375        self.cat_list = cat_list
376       
377        self.cat_text = wx.StaticText(self, label = "Current categories: ")
378        self.current_categories = wx.ListBox(self, 
379                                             choices = self.current_cats
380                                             , size=(300, 100))
381        self.existing_check = wx.RadioButton(self, 
382                                             label = 'Choose Existing')
383        self.new_check = wx.RadioButton(self, label = 'Create new')
384        self.exist_combo = wx.ComboBox(self, style = wx.CB_READONLY, 
385                                       size=(220,-1), choices = cat_list)
386        self.exist_combo.SetSelection(0)
387       
388       
389        self.remove_sb = wx.StaticBox(self, label = "Remove Category")
390       
391        self.remove_sb_sizer = wx.StaticBoxSizer(self.remove_sb, 
392                                                 wx.VERTICAL)
393
394        self.new_text = wx.TextCtrl(self, size=(220, -1))
395        self.ok_button = wx.Button(self, wx.ID_OK, "Done")
396        self.add_button = wx.Button(self, label = "Add")
397        self.add_button.Bind(wx.EVT_BUTTON, self.on_add)
398        self.remove_button = wx.Button(self, label = "Remove Selected")
399        self.remove_button.Bind(wx.EVT_BUTTON, self.on_remove)
400
401        self.existing_check.Bind(wx.EVT_RADIOBUTTON, self.on_existing)
402        self.new_check.Bind(wx.EVT_RADIOBUTTON, self.on_newcat)
403        self.existing_check.SetValue(True)
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        gs.AddMany( [ (self.existing_check, 5, wx.ALL),
411                      (self.exist_combo, 5, wx.ALL),
412                      (self.new_check, 5, wx.ALL),
413                      (self.new_text, 5, wx.ALL ),
414                      ((-1,-1)),
415                      (self.add_button, 5, wx.ALL | wx.ALIGN_RIGHT) ] )
416
417        self.add_sb_sizer.Add(gs, proportion = 1, flag = wx.ALL, border = 5)
418        vbox.Add(self.add_sb_sizer, flag = wx.ALL | wx.EXPAND, border = 10)
419
420        self.remove_sb_sizer.Add(self.remove_button, border = 5, 
421                                 flag = wx.ALL | wx.ALIGN_RIGHT)
422        vbox.Add(self.remove_sb_sizer, 
423                 flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 
424                 border = 10)
425        vbox.Add(self.ok_button, flag = wx.ALL | wx.ALIGN_RIGHT, 
426                 border = 10)
427       
428        if self.current_categories.GetCount() > 0:
429                self.current_categories.SetSelection(0)
430        self.new_text.Disable()
431        self.SetSizer(vbox)
432        self.Centre()
433        self.Show(True)
434
435    def on_add(self, event):
436        """
437        Callback for new category added
438        """
439        new_cat = ''
440        if self.existing_check.GetValue():
441            new_cat = str(self.exist_combo.GetValue())
442        else:
443            new_cat = str(self.new_text.GetValue())
444            if new_cat in self.cat_list:
445                wx.MessageBox('%s is already a model' % new_cat, 'Error',
446                              wx.OK | wx.ICON_EXCLAMATION )
447                return
448
449        if new_cat in self.current_cats:
450            wx.MessageBox('%s is already included in this model' \
451                              % new_cat, 'Error',
452                          wx.OK | wx.ICON_EXCLAMATION )
453            return
454
455        self.current_cats.append(new_cat)
456        self.current_categories.SetItems(self.current_cats)
457           
458       
459    def on_remove(self, event):
460        """
461        Callback for a category removed
462        """
463        if self.current_categories.GetSelection() == wx.NOT_FOUND:
464            wx.MessageBox('Please select a category to remove', 'Error',
465                          wx.OK | wx.ICON_EXCLAMATION )
466        else:
467            self.current_categories.Delete( \
468                self.current_categories.GetSelection())
469            self.current_cats = self.current_categories.GetItems()
470
471       
472
473    def on_newcat(self, event):
474        """
475        Callback for new category added
476        """
477        self.new_text.Enable()
478        self.exist_combo.Disable()
479
480
481    def on_existing(self, event):   
482        """
483        Callback for existing category selected
484        """
485        self.new_text.Disable()
486        self.exist_combo.Enable()
487
488    def get_category(self):
489        """
490        Returns a list of categories applying to this model
491        """
492        if not self.current_cats:
493            self.current_cats.append("Uncategorized")
494
495        ret = list()
496        for cat in self.current_cats:
497            ret.append(str(cat))
498        return ret
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
Note: See TracBrowser for help on using the repository browser.