source: sasview/src/sas/qtgui/MainWindow/CategoryManager.py @ 5a2bb75

Last change on this file since 5a2bb75 was 3d18691, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

New category manager design

  • Property mode set to 100644
File size: 14.0 KB
Line 
1import json
2import os
3
4from PyQt5 import QtGui
5from PyQt5 import QtCore
6from PyQt5 import QtWidgets
7
8from collections import defaultdict
9from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller
10from sasmodels.sasview_model import load_standard_models
11
12from .UI.CategoryManagerUI import Ui_CategoryManagerUI
13from .UI.ChangeCategoryUI import Ui_ChangeCategoryUI
14
15class ToolTippedItemModel(QtGui.QStandardItemModel):
16    """
17    Subclass from QStandardItemModel to allow displaying tooltips in
18    QTableView model.
19    """
20    def __init__(self, parent=None):
21        QtGui.QStandardItemModel.__init__(self, parent)
22
23    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
24        """
25        Displays tooltip for each column's header
26        :param section:
27        :param orientation:
28        :param role:
29        :return:
30        """
31        if role == QtCore.Qt.ToolTipRole:
32            if orientation == QtCore.Qt.Horizontal:
33                return str(self.header_tooltips[section])
34
35        return QtGui.QStandardItemModel.headerData(self, section, orientation, role)
36
37class Categories(object):
38    """
39    Container class for accessing model categories
40    """
41    def __init__(self):
42        self.master_category_dict = defaultdict(list)
43        self.by_model_dict = defaultdict(list)
44        self.model_enabled_dict = defaultdict(bool)
45        self.models = {}
46
47        # Prepare the master category list
48        self.readCategoryInfo()
49
50        # Prepare model->category lookup
51        self.setupModelDict()
52
53    def readCategoryInfo(self):
54        """
55        Reads the categories in from file
56        """
57        categorization_file = CategoryInstaller.get_user_file()
58        if not os.path.isfile(categorization_file):
59            categorization_file = CategoryInstaller.get_default_file()
60        with open(categorization_file, 'rb') as cat_file:
61            self.master_category_dict = json.load(cat_file)
62            self.regenerateModelDict()
63
64        self.category_list = sorted(self.master_category_dict.keys())
65
66    def saveCategories(self):
67        """
68        Serializes categorization info to file
69        """
70        self.regenerateMasterDict()
71        with open(CategoryInstaller.get_user_file(), 'w') as cat_file:
72            json.dump(self.master_category_dict, cat_file)
73
74    def setupModelDict(self):
75        """
76        create a dictionary for model->category mapping
77        """
78        # Load the model dict
79        models = load_standard_models()
80        for model in models:
81            # {model name -> model object}
82            self.models[model.name] = model
83
84    def regenerateModelDict(self):
85        """
86        Regenerates self.by_model_dict which has each model name as the
87        key and the list of categories belonging to that model
88        along with the enabled mapping
89        """
90        self.by_model_dict = defaultdict(list)
91        for category in self.master_category_dict:
92            for (model, enabled) in self.master_category_dict[category]:
93                self.by_model_dict[model].append(category)
94                self.model_enabled_dict[model] = enabled
95
96    def regenerateMasterDict(self):
97        """
98        regenerates self.master_category_dict from
99        self.by_model_dict and self.model_enabled_dict
100        """
101        self.master_category_dict = defaultdict(list)
102        for model in self.by_model_dict:
103            for category in self.by_model_dict[model]:
104                self.master_category_dict[category].append\
105                    ((model, self.model_enabled_dict[model]))
106
107    def modelToCategory(self):
108        """
109        Getter for the model->category dict
110        """
111        return self.by_model_dict
112
113    def modelDict(self):
114        """
115        Getter for the model list
116        """
117        return self.models
118
119    def categoryDict(self):
120        """
121        Getter for the category dict
122        """
123        return self.master_category_dict
124
125    def categoryList(self):
126        """
127        Getter for the category list
128        """
129        return self.category_list
130
131
132
133class CategoryManager(QtWidgets.QDialog, Ui_CategoryManagerUI):
134    def __init__(self, parent=None, manager=None):
135        super(CategoryManager, self).__init__(parent)
136        self.setupUi(self)
137
138        self.communicator = manager.communicator()
139
140        self.setWindowTitle("Category Manager")
141
142        self.initializeGlobals()
143
144        self.initializeModels()
145
146        self.initializeSignals()
147
148    def initializeGlobals(self):
149        """
150        Initialize global variables used in this class
151        """
152        # Default checked state
153        self.chkEnable.setCheckState(QtCore.Qt.Checked)
154
155        # Modify is disabled by default
156        self.cmdModify.setEnabled(False)
157
158        # Data for chosen model
159        self.model_data = None
160
161        # Categories object
162        self.categories = Categories()
163
164    def initializeModels(self):
165        """
166        Set up models and views
167        """
168        # Set the main models
169        self._category_model = ToolTippedItemModel()
170        self.lstCategory.setModel(self._category_model)
171        # Proxy model for showing a subset of model content
172        self.model_proxy = QtCore.QSortFilterProxyModel(self)
173        self.model_proxy.setSourceModel(self._category_model)
174        self.lstCategory.setModel(self.model_proxy)
175
176        self.initializeModelList()
177
178        self.setTableProperties(self.lstCategory)
179
180        self.lstCategory.setAlternatingRowColors(True)
181        # self.lstCategory.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
182        # self.lstCategory.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
183
184    def initializeModelList(self):
185        """
186        Model category combo setup
187        """
188        self._category_model.clear()
189        for ind, model in enumerate(self.categories.modelDict()):
190            item = QtGui.QStandardItem(model)
191            empty_item = QtGui.QStandardItem()
192            empty_item.setEditable(False)
193            # Add a checkbox to it
194            item.setCheckable(True)
195            item.setCheckState(QtCore.Qt.Checked)
196            item.setEditable(False)
197            current_category = self.categories.modelToCategory()[model]
198            self._category_model.appendRow([item, empty_item])
199            self._category_model.item(ind, 1).setText(', '.join(i for i in current_category))
200
201    def initializeSignals(self):
202        """
203        :return:
204        """
205        self.cmdOK.clicked.connect(self.onClose)
206        self.cmdModify.clicked.connect(self.onModify)
207        self.cmdReset.clicked.connect(self.onReset)
208
209        self.chkEnable.toggled.connect(self.onEnableAll)
210
211        # every change in txtSearch
212        self.txtSearch.textChanged.connect(self.onSearch)
213
214        # Signals from the list
215        selectionModel = self.lstCategory.selectionModel()
216        selectionModel.selectionChanged.connect(self.onListSelection)
217
218
219    def onClose(self):
220        """
221        Save the category file before exiting
222        """
223        self.categories.saveCategories()
224        # Ask the fitting widget to reload the comboboxes
225        self.communicator.updateModelCategoriesSignal.emit()
226
227        self.close()
228
229    def selectedModels(self):
230        """
231        Returns a list of selected models
232        """
233        selected_models = []
234        selectionModel = self.lstCategory.selectionModel()
235        selectedRows = selectionModel.selectedRows()
236        for row in selectedRows:
237            model_index = self.model_proxy.mapToSource(row)
238            current_text = self._category_model.itemFromIndex(model_index).text()
239            selected_models.append(self.categories.modelDict()[current_text])
240        return selected_models
241
242    def onListSelection(self):
243        """
244        Respond to row selection and update GUI
245        """
246        selected_items = self.selectedModels()
247        self.cmdModify.setEnabled(len(selected_items) == 1)
248
249    def onReset(self):
250        """
251        Reload the saved categories
252        """
253        self.initializeGlobals()
254        # Reload the Categories object
255        self.categories = Categories()
256        # Reload the model
257        self.initializeModelList()
258        self.setTableProperties(self.lstCategory)
259        self.lstCategory.setAlternatingRowColors(True)
260
261    def onEnableAll(self, isChecked):
262        """
263        Respond to the Enable/Disable All checkbox
264        """
265        select = QtCore.Qt.Checked if isChecked else QtCore.Qt.Unchecked
266        for row in range(self._category_model.rowCount()):
267            self._category_model.item(row).setCheckState(select)
268
269    def onSearch(self):
270        """
271        Respond to text entered in search field
272        """
273        if self.txtSearch.isModified():
274            input_to_check = str(self.txtSearch.text())
275
276        # redefine the proxy model
277        self.model_proxy.setFilterRegExp(QtCore.QRegExp(input_to_check,
278                         QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString))
279
280    def onModify(self):
281        """
282        Show the Change Category dialog - modal
283        """
284        # Selected category:
285        selected_models = self.selectedModels()
286        if len(selected_models) > 1:
287            # Somehow we got more than one model - complain!
288            raise AttributeError("Please select only one model.")
289        change_dialog = ChangeCategory(parent=self, categories=self.categories, model=selected_models[0])
290
291        if change_dialog.exec_() != QtWidgets.QDialog.Accepted:
292            return
293        # Reload the model
294        self.initializeModelList()
295        self.setTableProperties(self.lstCategory)
296        self.lstCategory.setAlternatingRowColors(True)
297
298    def setTableProperties(self, table):
299        """
300        Setting table properties
301        """
302        # Table properties
303        table.verticalHeader().setVisible(False)
304        table.setAlternatingRowColors(True)
305        #table.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
306        table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
307        table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
308        table.resizeColumnsToContents()
309
310        # Header
311        header = table.horizontalHeader()
312        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
313        header.ResizeMode(QtWidgets.QHeaderView.Interactive)
314
315
316        self._category_model.setHeaderData(0, QtCore.Qt.Horizontal, "Model")
317        self._category_model.setHeaderData(1, QtCore.Qt.Horizontal, "Category")
318
319        self._category_model.header_tooltips = ['Select model',
320                                                'Change or create new category']
321
322class ChangeCategory(QtWidgets.QDialog, Ui_ChangeCategoryUI):
323    """
324    Dialog for adding/removing categories for a single model
325    """
326    def __init__(self, parent=None, categories=None, model=None):
327        super(ChangeCategory, self).__init__(parent)
328        self.setupUi(self)
329
330        self.model = model
331        self.parent = parent
332        self.categories = categories
333
334        self.initializeElements()
335
336        self.initializeList()
337
338        self.initializeSignals()
339
340    def initializeElements(self):
341        """
342        Initialize local GUI elements with information from the Categories object
343        """
344        self.cbCategories.addItems(self.categories.categoryList())
345        self.setWindowTitle("Change Category for: "+ self.model.name)
346        self.lblTitle.setText("Current categories for " + self.model.name)
347        self.rbExisting.setChecked(True)
348        self.onAddChoice()
349
350    def initializeList(self):
351        """
352        Initialize the category list for the given model
353        """
354        current_categories = self.categories.modelToCategory()[self.model.name]
355        for cat in current_categories:
356            self.lstCategories.addItem(cat)
357
358    def initializeSignals(self):
359        """
360        Initialize signals for UI elements
361        """
362        self.cmdAdd.clicked.connect(self.onAdd)
363        self.cmdRemove.clicked.connect(self.onRemove)
364        self.cmdOK.clicked.connect(self.onOK)
365        self.cmdCancel.clicked.connect(self.close)
366
367        # Signals from the list
368        self.lstCategories.itemSelectionChanged.connect(self.onListSelection)
369
370        # Signals from the radio buttons
371        self.rbExisting.toggled.connect(self.onAddChoice)
372
373    def onAddChoice(self):
374        """
375        Respond to the type selection for new category
376        """
377        isNew = self.rbNew.isChecked()
378        self.cbCategories.setEnabled(not isNew)
379        self.txtNewCategory.setEnabled(isNew)
380
381    def onListSelection(self):
382        """
383        Respond to selection in the category list view
384        """
385        selected_items = self.selectedModels()
386        self.cmdRemove.setEnabled(len(selected_items) > 0)
387
388    def selectedModels(self):
389        """
390        Returns a list of selected models
391        """
392        selected_categories = []
393        selectedRows = self.lstCategories.selectedItems()
394        selected_categories = [str(row.text()) for row in selectedRows]
395
396        return selected_categories
397
398    def onAdd(self):
399        """
400        Add the chosen category to the list
401        """
402        if self.rbExisting.isChecked():
403            new_category = self.cbCategories.currentText()
404        else:
405            new_category = self.txtNewCategory.text()
406        # Display the current value as txt
407        if new_category:
408            self.lstCategories.addItem(new_category)
409
410    def onRemove(self):
411        """
412        Remove selected categories in the list
413        """
414        selectedRows = self.lstCategories.selectedItems()
415        if not selectedRows:
416            return
417        for row in selectedRows:
418            self.lstCategories.takeItem(self.lstCategories.row(row))
419
420    def onOK(self):
421        """
422        Accept the new categories for the model
423        """
424        # Read in the categories
425        self.categories.modelToCategory()[self.model.name] = self.listCategories()
426        self.accept()
427
428    def listCategories(self):
429        """
430        Returns the list of categories from the QListWidget
431        """
432        return [str(self.lstCategories.item(i).text()) for i in range(self.lstCategories.count())]
Note: See TracBrowser for help on using the repository browser.