source: sasview/src/sas/qtgui/MainWindow/CategoryManager.py @ 33c0561

ESS_GUIESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_sync_sascalc
Last change on this file since 33c0561 was 33c0561, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Replace Apply button menu driven functionality with additional button.
Removed Cancel.
Removed the window system context help button from all affected widgets.
SASVIEW-1239

  • Property mode set to 100644
File size: 14.2 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        # disable the context help icon
139        self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
140
141        self.communicator = manager.communicator()
142
143        self.setWindowTitle("Category Manager")
144
145        self.initializeGlobals()
146
147        self.initializeModels()
148
149        self.initializeSignals()
150
151    def initializeGlobals(self):
152        """
153        Initialize global variables used in this class
154        """
155        # Default checked state
156        self.chkEnable.setCheckState(QtCore.Qt.Checked)
157
158        # Modify is disabled by default
159        self.cmdModify.setEnabled(False)
160
161        # Data for chosen model
162        self.model_data = None
163
164        # Categories object
165        self.categories = Categories()
166
167    def initializeModels(self):
168        """
169        Set up models and views
170        """
171        # Set the main models
172        self._category_model = ToolTippedItemModel()
173        self.lstCategory.setModel(self._category_model)
174        # Proxy model for showing a subset of model content
175        self.model_proxy = QtCore.QSortFilterProxyModel(self)
176        self.model_proxy.setSourceModel(self._category_model)
177        self.lstCategory.setModel(self.model_proxy)
178
179        self.initializeModelList()
180
181        self.setTableProperties(self.lstCategory)
182
183        self.lstCategory.setAlternatingRowColors(True)
184        # self.lstCategory.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
185        # self.lstCategory.setAttribute(QtCore.Qt.WA_MacShowFocusRect, False)
186
187    def initializeModelList(self):
188        """
189        Model category combo setup
190        """
191        self._category_model.clear()
192        for ind, model in enumerate(self.categories.modelDict()):
193            item = QtGui.QStandardItem(model)
194            empty_item = QtGui.QStandardItem()
195            empty_item.setEditable(False)
196            # Add a checkbox to it
197            item.setCheckable(True)
198            item.setCheckState(QtCore.Qt.Checked)
199            item.setEditable(False)
200            current_category = self.categories.modelToCategory()[model]
201            self._category_model.appendRow([item, empty_item])
202            self._category_model.item(ind, 1).setText(', '.join(i for i in current_category))
203
204    def initializeSignals(self):
205        """
206        :return:
207        """
208        self.cmdOK.clicked.connect(self.onClose)
209        self.cmdModify.clicked.connect(self.onModify)
210        self.cmdReset.clicked.connect(self.onReset)
211
212        self.chkEnable.toggled.connect(self.onEnableAll)
213
214        # every change in txtSearch
215        self.txtSearch.textChanged.connect(self.onSearch)
216
217        # Signals from the list
218        selectionModel = self.lstCategory.selectionModel()
219        selectionModel.selectionChanged.connect(self.onListSelection)
220
221
222    def onClose(self):
223        """
224        Save the category file before exiting
225        """
226        self.categories.saveCategories()
227        # Ask the fitting widget to reload the comboboxes
228        self.communicator.updateModelCategoriesSignal.emit()
229
230        self.close()
231
232    def selectedModels(self):
233        """
234        Returns a list of selected models
235        """
236        selected_models = []
237        selectionModel = self.lstCategory.selectionModel()
238        selectedRows = selectionModel.selectedRows()
239        for row in selectedRows:
240            model_index = self.model_proxy.mapToSource(row)
241            current_text = self._category_model.itemFromIndex(model_index).text()
242            selected_models.append(self.categories.modelDict()[current_text])
243        return selected_models
244
245    def onListSelection(self):
246        """
247        Respond to row selection and update GUI
248        """
249        selected_items = self.selectedModels()
250        self.cmdModify.setEnabled(len(selected_items) == 1)
251
252    def onReset(self):
253        """
254        Reload the saved categories
255        """
256        self.initializeGlobals()
257        # Reload the Categories object
258        self.categories = Categories()
259        # Reload the model
260        self.initializeModelList()
261        self.setTableProperties(self.lstCategory)
262        self.lstCategory.setAlternatingRowColors(True)
263
264    def onEnableAll(self, isChecked):
265        """
266        Respond to the Enable/Disable All checkbox
267        """
268        select = QtCore.Qt.Checked if isChecked else QtCore.Qt.Unchecked
269        for row in range(self._category_model.rowCount()):
270            self._category_model.item(row).setCheckState(select)
271
272    def onSearch(self):
273        """
274        Respond to text entered in search field
275        """
276        if self.txtSearch.isModified():
277            input_to_check = str(self.txtSearch.text())
278
279        # redefine the proxy model
280        self.model_proxy.setFilterRegExp(QtCore.QRegExp(input_to_check,
281                         QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString))
282
283    def onModify(self):
284        """
285        Show the Change Category dialog - modal
286        """
287        # Selected category:
288        selected_models = self.selectedModels()
289        if len(selected_models) > 1:
290            # Somehow we got more than one model - complain!
291            raise AttributeError("Please select only one model.")
292        change_dialog = ChangeCategory(parent=self, categories=self.categories, model=selected_models[0])
293
294        if change_dialog.exec_() != QtWidgets.QDialog.Accepted:
295            return
296        # Reload the model
297        self.initializeModelList()
298        self.setTableProperties(self.lstCategory)
299        self.lstCategory.setAlternatingRowColors(True)
300
301    def setTableProperties(self, table):
302        """
303        Setting table properties
304        """
305        # Table properties
306        table.verticalHeader().setVisible(False)
307        table.setAlternatingRowColors(True)
308        #table.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding)
309        table.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
310        table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
311        table.resizeColumnsToContents()
312
313        # Header
314        header = table.horizontalHeader()
315        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
316        header.ResizeMode(QtWidgets.QHeaderView.Interactive)
317
318
319        self._category_model.setHeaderData(0, QtCore.Qt.Horizontal, "Model")
320        self._category_model.setHeaderData(1, QtCore.Qt.Horizontal, "Category")
321
322        self._category_model.header_tooltips = ['Select model',
323                                                'Change or create new category']
324
325class ChangeCategory(QtWidgets.QDialog, Ui_ChangeCategoryUI):
326    """
327    Dialog for adding/removing categories for a single model
328    """
329    def __init__(self, parent=None, categories=None, model=None):
330        super(ChangeCategory, self).__init__(parent)
331        self.setupUi(self)
332        # disable the context help icon
333        self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
334
335        self.model = model
336        self.parent = parent
337        self.categories = categories
338
339        self.initializeElements()
340
341        self.initializeList()
342
343        self.initializeSignals()
344
345    def initializeElements(self):
346        """
347        Initialize local GUI elements with information from the Categories object
348        """
349        self.cbCategories.addItems(self.categories.categoryList())
350        self.setWindowTitle("Change Category for: "+ self.model.name)
351        self.lblTitle.setText("Current categories for " + self.model.name)
352        self.rbExisting.setChecked(True)
353        self.onAddChoice()
354
355    def initializeList(self):
356        """
357        Initialize the category list for the given model
358        """
359        current_categories = self.categories.modelToCategory()[self.model.name]
360        for cat in current_categories:
361            self.lstCategories.addItem(cat)
362
363    def initializeSignals(self):
364        """
365        Initialize signals for UI elements
366        """
367        self.cmdAdd.clicked.connect(self.onAdd)
368        self.cmdRemove.clicked.connect(self.onRemove)
369        self.cmdOK.clicked.connect(self.onOK)
370        self.cmdCancel.clicked.connect(self.close)
371
372        # Signals from the list
373        self.lstCategories.itemSelectionChanged.connect(self.onListSelection)
374
375        # Signals from the radio buttons
376        self.rbExisting.toggled.connect(self.onAddChoice)
377
378    def onAddChoice(self):
379        """
380        Respond to the type selection for new category
381        """
382        isNew = self.rbNew.isChecked()
383        self.cbCategories.setEnabled(not isNew)
384        self.txtNewCategory.setEnabled(isNew)
385
386    def onListSelection(self):
387        """
388        Respond to selection in the category list view
389        """
390        selected_items = self.selectedModels()
391        self.cmdRemove.setEnabled(len(selected_items) > 0)
392
393    def selectedModels(self):
394        """
395        Returns a list of selected models
396        """
397        selected_categories = []
398        selectedRows = self.lstCategories.selectedItems()
399        selected_categories = [str(row.text()) for row in selectedRows]
400
401        return selected_categories
402
403    def onAdd(self):
404        """
405        Add the chosen category to the list
406        """
407        if self.rbExisting.isChecked():
408            new_category = self.cbCategories.currentText()
409        else:
410            new_category = self.txtNewCategory.text()
411        # Display the current value as txt
412        if new_category:
413            self.lstCategories.addItem(new_category)
414
415    def onRemove(self):
416        """
417        Remove selected categories in the list
418        """
419        selectedRows = self.lstCategories.selectedItems()
420        if not selectedRows:
421            return
422        for row in selectedRows:
423            self.lstCategories.takeItem(self.lstCategories.row(row))
424
425    def onOK(self):
426        """
427        Accept the new categories for the model
428        """
429        # Read in the categories
430        self.categories.modelToCategory()[self.model.name] = self.listCategories()
431        self.accept()
432
433    def listCategories(self):
434        """
435        Returns the list of categories from the QListWidget
436        """
437        return [str(self.lstCategories.item(i).text()) for i in range(self.lstCategories.count())]
Note: See TracBrowser for help on using the repository browser.