Changeset 693aaf79 in sasview


Ignore:
Timestamp:
Jul 19, 2018 4:36:51 AM (5 weeks ago)
Author:
Piotr Rozyczko <rozyczko@…>
Branches:
ESS_GUI_CategroyManager
Children:
b50f120
Parents:
ec34023
Message:

Remade the Category Manager so it supports multiple categories.
UI similar to the original version.

Location:
src/sas/qtgui
Files:
1 added
1 deleted
5 edited

Legend:

Unmodified
Added
Removed
  • src/sas/qtgui/MainWindow/CategoryManager.py

    rec34023 r693aaf79  
    11import json 
    22import os 
    3 import functools 
    43 
    54from PyQt5 import QtGui 
     
    98from collections import defaultdict 
    109from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller 
    11 from sas.qtgui.MainWindow.ViewDelegate import CategoryViewDelegate 
    1210from sasmodels.sasview_model import load_standard_models 
    1311 
    1412from .UI.CategoryManagerUI import Ui_CategoryManagerUI 
     13from .UI.ChangeCategoryUI import Ui_ChangeCategoryUI 
    1514 
    1615class ToolTippedItemModel(QtGui.QStandardItemModel): 
     
    2019    """ 
    2120    def __init__(self, parent=None): 
    22         QtGui.QStandardItemModel.__init__(self,parent) 
     21        QtGui.QStandardItemModel.__init__(self, parent) 
    2322 
    2423    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole): 
     
    3635        return QtGui.QStandardItemModel.headerData(self, section, orientation, role) 
    3736 
    38  
    39 class CategoryManager(QtWidgets.QDialog, Ui_CategoryManagerUI): 
    40     def __init__(self, parent=None): 
    41         super(CategoryManager, self).__init__(parent) 
    42         self.setupUi(self) 
    43  
    44         self.setWindowTitle("Category Manager") 
    45  
    46         self.initializeGlobals() 
    47  
    48         self.initializeModels() 
    49  
    50         self.initializeSignals() 
    51  
    52         #self.addActions() 
    53  
    54     def initializeGlobals(self): 
    55         """ 
    56         Initialize global variables used in this class 
    57         """ 
    58         # SasModel is loaded 
    59         self.model_is_loaded = False 
    60         # Data[12]D passed and set 
    61         self._previous_category_index = 0 
    62         # Utility variable for multishell display 
    63         self.models = {} 
    64         # Parameters to fit 
    65         self.undo_supported = False 
    66         self.page_stack = [] 
    67         self.all_data = [] 
    68  
    69         # Default checked state 
    70         self.chkEnable.setCheckState(QtCore.Qt.Checked) 
    71  
    72         # Data for chosen model 
    73         self.model_data = None 
    74  
    75     def readCategoryInfo(self): 
    76         """ 
    77         Reads the categories in from file 
    78         """ 
     37class Categories(object): 
     38    """ 
     39    Container class for accessing model categories 
     40    """ 
     41    def __init__(self): 
    7942        self.master_category_dict = defaultdict(list) 
    8043        self.by_model_dict = defaultdict(list) 
    8144        self.model_enabled_dict = defaultdict(bool) 
    82  
     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        """ 
    8357        categorization_file = CategoryInstaller.get_user_file() 
    8458        if not os.path.isfile(categorization_file): 
     
    9064        self.category_list = sorted(self.master_category_dict.keys()) 
    9165 
     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        """ 
    9278        # Load the model dict 
    9379        models = load_standard_models() 
    94         # Reverse {map model -> category} 
    95         self.model_to_category = {} 
    9680        for model in models: 
    9781            # {model name -> model object} 
    9882            self.models[model.name] = model 
    99             # find model.name in self.master_category_dict 
    100             for key, value in self.master_category_dict.items(): 
    101                 # value is a list of [[model_name, bool],...] 
    102                 if model.name in [name[0] for name in value]: 
    103                     self.model_to_category[model.name] = key 
    104                     break 
     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() 
    105163 
    106164    def initializeModels(self): 
     
    109167        """ 
    110168        # Set the main models 
    111         # We can't use a single model here, due to restrictions on flattening 
    112         # the model tree with subclassed QAbstractProxyModel... 
    113169        self._category_model = ToolTippedItemModel() 
    114170        self.lstCategory.setModel(self._category_model) 
    115         self.readCategoryInfo() 
     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 
    116176        self.initializeModelList() 
     177 
    117178        self.setTableProperties(self.lstCategory) 
    118179 
    119         # Delegates for custom editing and display 
    120         self.lstCategory.setItemDelegate(CategoryViewDelegate(self)) 
    121         # 
    122180        self.lstCategory.setAlternatingRowColors(True) 
    123181        # self.lstCategory.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) 
     
    128186        Model category combo setup 
    129187        """ 
    130         #Move current category to the top of the list and then continue alphablitecaly 
    131  
    132         for model in self.models: 
     188        self._category_model.clear() 
     189        for ind, model in enumerate(self.categories.modelDict()): 
    133190            item = QtGui.QStandardItem(model) 
    134191            empty_item = QtGui.QStandardItem() 
     
    137194            item.setCheckState(QtCore.Qt.Checked) 
    138195            item.setEditable(False) 
     196            current_category = self.categories.modelToCategory()[model] 
    139197            self._category_model.appendRow([item, empty_item]) 
    140  
    141         # Proxy model for showing a subset of model content 
    142         self.model_proxy = QtCore.QSortFilterProxyModel(self) 
    143         self.model_proxy.setSourceModel(self._category_model) 
    144         self.lstCategory.setModel(self.model_proxy) 
    145  
    146         for ind, model in enumerate(self.models): 
    147             current_category = self.model_to_category[model] 
    148             self._category_model.item(ind,1).setText(current_category) 
    149  
    150     def onCategoryChange(self, index, row): 
    151         """ 
    152         """ 
    153         # New category from the delegate 
    154         new_category = self.category_list[index] 
    155         # Current item from model 
    156         model = self._category_model.item(row, 0).text() 
    157         self.model_to_category[model] = new_category 
    158  
    159         # Display the current value as txt 
    160         self._category_model.item(row,1).setText(new_category) 
    161  
    162         # Update the backend 
    163         # ??? 
    164  
    165     def onNewCategory(self, text): 
    166         """ 
    167         When the new category is created 
     198            self._category_model.item(ind, 1).setText(', '.join(i for i in current_category)) 
     199 
     200    def initializeSignals(self): 
     201        """ 
    168202        :return: 
    169203        """ 
    170         # editTextChanged() has to be caught 
    171         # When text is edited category_list needs to be updated and models initialized again 
    172         #text = self.cbCategory.currentText() 
    173         self.category_list.append(text) 
    174         self.initializeModelList() 
    175  
    176     def enableModelCombo(self): 
    177         """ Enable the combobox """ 
    178         self.cbModel.setEnabled(True) 
    179         self.lblModel.setEnabled(True) 
    180  
    181     def regenerateModelDict(self): 
    182         """ 
    183         Regenerates self.by_model_dict which has each model name as the 
    184         key and the list of categories belonging to that model 
    185         along with the enabled mapping 
    186         """ 
    187         self.by_model_dict = defaultdict(list) 
    188         for category in self.master_category_dict: 
    189             for (model, enabled) in self.master_category_dict[category]: 
    190                 self.by_model_dict[model].append(category) 
    191                 self.model_enabled_dict[model] = enabled 
    192  
    193     def initializeSignals(self): 
    194         """ 
    195         :return: 
    196         """ 
    197         #self.cmdOK.clicked.connect(self.close) 
     204        self.cmdOK.clicked.connect(self.onClose) 
     205        self.cmdModify.clicked.connect(self.onModify) 
     206        self.cmdReset.clicked.connect(self.onReset) 
     207 
    198208        self.chkEnable.toggled.connect(self.onEnableAll) 
    199209 
     
    201211        self.txtSearch.textChanged.connect(self.onSearch) 
    202212 
    203         # signals from the delegate 
    204         self.lstCategory.itemDelegate().combo_updated.connect(self.onCategoryChange) 
     213        # Signals from the list 
     214        selectionModel = self.lstCategory.selectionModel() 
     215        selectionModel.selectionChanged.connect(self.onListSelection) 
     216 
     217 
     218    def onClose(self): 
     219        """ 
     220        Save the category file before exiting 
     221        """ 
     222        self.categories.saveCategories() 
     223        # Ask the fitting widget to reload the comboboxes 
     224        self.communicator.updateModelCategoriesSignal.emit() 
     225 
     226        self.close() 
     227 
     228    def selectedModels(self): 
     229        """ 
     230        Returns a list of selected models 
     231        """ 
     232        selected_models = [] 
     233        selectionModel = self.lstCategory.selectionModel() 
     234        selectedRows = selectionModel.selectedRows() 
     235        for row in selectedRows: 
     236            model_index = self.model_proxy.mapToSource(row) 
     237            current_text = self._category_model.itemFromIndex(model_index).text() 
     238            selected_models.append(self.categories.modelDict()[current_text]) 
     239        return selected_models 
     240 
     241    def onListSelection(self): 
     242        """ 
     243        Respond to row selection and update GUI 
     244        """ 
     245        selected_items = self.selectedModels() 
     246        self.cmdModify.setEnabled(len(selected_items) == 1) 
     247 
     248    def onReset(self): 
     249        """ 
     250        Reload the saved categories 
     251        """ 
     252        self.initializeGlobals() 
     253        # Reload the Categories object 
     254        self.categories = Categories() 
     255        # Reload the model 
     256        self.initializeModelList() 
     257        self.setTableProperties(self.lstCategory) 
     258        self.lstCategory.setAlternatingRowColors(True) 
    205259 
    206260    def onEnableAll(self, isChecked): 
     
    211265        for row in range(self._category_model.rowCount()): 
    212266            self._category_model.item(row).setCheckState(select) 
    213         pass 
    214267 
    215268    def onSearch(self): 
     
    222275        # redefine the proxy model 
    223276        self.model_proxy.setFilterRegExp(QtCore.QRegExp(input_to_check, 
    224                           QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString)) 
     277                         QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString)) 
     278 
     279    def onModify(self): 
     280        """ 
     281        Show the Change Category dialog - modal 
     282        """ 
     283        # Selected category: 
     284        selected_models = self.selectedModels() 
     285        if len(selected_models) > 1: 
     286            # Somehow we got more than one model - complain! 
     287            raise AttributeError("Please select only one model.") 
     288        change_dialog = ChangeCategory(parent=self, categories=self.categories, model=selected_models[0]) 
     289 
     290        if change_dialog.exec_() != QtWidgets.QDialog.Accepted: 
     291            return 
     292        # Reload the model 
     293        self.initializeModelList() 
     294        self.setTableProperties(self.lstCategory) 
     295        self.lstCategory.setAlternatingRowColors(True) 
    225296 
    226297    def setTableProperties(self, table): 
     
    248319                                                'Change or create new category'] 
    249320 
    250         #self.lstCategory.header().setFont(self.boldFont) 
     321class ChangeCategory(QtWidgets.QDialog, Ui_ChangeCategoryUI): 
     322    """ 
     323    Dialog for adding/removing categories for a single model 
     324    """ 
     325    def __init__(self, parent=None, categories=None, model=None): 
     326        super(ChangeCategory, self).__init__(parent) 
     327        self.setupUi(self) 
     328 
     329        self.model = model 
     330        self.parent = parent 
     331        self.categories = categories 
     332 
     333        self.initializeElements() 
     334 
     335        self.initializeList() 
     336 
     337        self.initializeSignals() 
     338 
     339    def initializeElements(self): 
     340        """ 
     341        Initialize local GUI elements with information from the Categories object 
     342        """ 
     343        self.cbCategories.addItems(self.categories.categoryList()) 
     344        self.setWindowTitle("Change Category for: "+ self.model.name) 
     345        self.lblTitle.setText("Current categories for " + self.model.name) 
     346        self.rbExisting.setChecked(True) 
     347        self.onAddChoice() 
     348 
     349    def initializeList(self): 
     350        """ 
     351        Initialize the category list for the given model 
     352        """ 
     353        current_categories = self.categories.modelToCategory()[self.model.name] 
     354        for cat in current_categories: 
     355            self.lstCategories.addItem(cat) 
     356 
     357    def initializeSignals(self): 
     358        """ 
     359        Initialize signals for UI elements 
     360        """ 
     361        self.cmdAdd.clicked.connect(self.onAdd) 
     362        self.cmdRemove.clicked.connect(self.onRemove) 
     363        self.cmdOK.clicked.connect(self.onOK) 
     364        self.cmdCancel.clicked.connect(self.close) 
     365 
     366        # Signals from the list 
     367        self.lstCategories.itemSelectionChanged.connect(self.onListSelection) 
     368 
     369        # Signals from the radio buttons 
     370        self.rbExisting.toggled.connect(self.onAddChoice) 
     371 
     372    def onAddChoice(self): 
     373        """ 
     374        Respond to the type selection for new category 
     375        """ 
     376        isNew = self.rbNew.isChecked() 
     377        self.cbCategories.setEnabled(not isNew) 
     378        self.txtNewCategory.setEnabled(isNew) 
     379 
     380    def onListSelection(self): 
     381        """ 
     382        Respond to selection in the category list view 
     383        """ 
     384        selected_items = self.selectedModels() 
     385        self.cmdRemove.setEnabled(len(selected_items) > 0) 
     386 
     387    def selectedModels(self): 
     388        """ 
     389        Returns a list of selected models 
     390        """ 
     391        selected_categories = [] 
     392        selectedRows = self.lstCategories.selectedItems() 
     393        selected_categories = [str(row.text()) for row in selectedRows] 
     394 
     395        return selected_categories 
     396 
     397    def onAdd(self): 
     398        """ 
     399        Add the chosen category to the list 
     400        """ 
     401        if self.rbExisting.isChecked(): 
     402            new_category = self.cbCategories.currentText() 
     403        else: 
     404            new_category = self.txtNewCategory.text() 
     405        # Display the current value as txt 
     406        if new_category: 
     407            self.lstCategories.addItem(new_category) 
     408 
     409    def onRemove(self): 
     410        """ 
     411        Remove selected categories in the list 
     412        """ 
     413        selectedRows = self.lstCategories.selectedItems() 
     414        if not selectedRows: 
     415            return 
     416        for row in selectedRows: 
     417            self.lstCategories.takeItem(self.lstCategories.row(row)) 
     418 
     419    def onOK(self): 
     420        """ 
     421        Accept the new categories for the model 
     422        """ 
     423        # Read in the categories 
     424        self.categories.modelToCategory()[self.model.name] = self.listCategories() 
     425        self.accept() 
     426 
     427    def listCategories(self): 
     428        """ 
     429        Returns the list of categories from the QListWidget 
     430        """ 
     431        return [str(self.lstCategories.item(i).text()) for i in range(self.lstCategories.count())] 
  • src/sas/qtgui/MainWindow/GuiManager.py

    r674bc4d r693aaf79  
    127127        self.ackWidget = Acknowledgements() 
    128128        self.aboutWidget = AboutBox() 
    129         self.categoryManagerWidget = CategoryManager() 
     129        self.categoryManagerWidget = CategoryManager(self._parent, manager=self) 
    130130        self.welcomePanel = WelcomePanel() 
    131131 
     
    550550        """ 
    551551        self.categoryManagerWidget.show() 
     552 
    552553    #============ TOOLS ================= 
    553554    def actionData_Operation(self): 
  • src/sas/qtgui/MainWindow/UI/CategoryManagerUI.ui

    rec34023 r693aaf79  
    77    <x>0</x> 
    88    <y>0</y> 
    9     <width>596</width> 
    10     <height>783</height> 
     9    <width>548</width> 
     10    <height>717</height> 
    1111   </rect> 
    1212  </property> 
     
    1919  <property name="windowTitle"> 
    2020   <string>Category Manager</string> 
    21   </property> 
    22   <property name="windowIcon"> 
    23    <iconset> 
    24     <normaloff>../../../../../../../../../../Users/UI/res/ball.ico</normaloff>../../../../../../../../../../Users/UI/res/ball.ico</iconset> 
    2521  </property> 
    2622  <layout class="QGridLayout" name="gridLayout_2"> 
     
    112108   <item row="1" column="0"> 
    113109    <layout class="QHBoxLayout" name="horizontalLayout"> 
    114      <property name="sizeConstraint"> 
    115       <enum>QLayout::SetMinimumSize</enum> 
    116      </property> 
    117110     <item> 
    118111      <spacer name="horizontalSpacer"> 
     
    129122     </item> 
    130123     <item> 
    131       <widget class="QPushButton" name="removeButton"> 
     124      <widget class="QPushButton" name="cmdModify"> 
     125       <property name="toolTip"> 
     126        <string>Add/Remove categories for the selected model.</string> 
     127       </property> 
    132128       <property name="text"> 
    133         <string>Remove Selected</string> 
     129        <string>Modify</string> 
    134130       </property> 
    135131      </widget> 
    136132     </item> 
    137133     <item> 
    138       <widget class="QPushButton" name="resetButton"> 
     134      <widget class="QPushButton" name="cmdReset"> 
     135       <property name="toolTip"> 
     136        <string>Reset categories for selected models.</string> 
     137       </property> 
    139138       <property name="text"> 
    140139        <string>Reset</string> 
     
    143142     </item> 
    144143     <item> 
    145       <widget class="QPushButton" name="helpButton"> 
     144      <widget class="QPushButton" name="cmdOK"> 
     145       <property name="text"> 
     146        <string>OK</string> 
     147       </property> 
     148       <property name="autoDefault"> 
     149        <bool>true</bool> 
     150       </property> 
     151      </widget> 
     152     </item> 
     153     <item> 
     154      <widget class="QPushButton" name="cmdHelp"> 
    146155       <property name="text"> 
    147156        <string>Help</string> 
     
    157166 </widget> 
    158167 <resources/> 
    159  <connections> 
    160   <connection> 
    161    <sender>resetButton</sender> 
    162    <signal>clicked()</signal> 
    163    <receiver>CategoryManagerUI</receiver> 
    164    <slot>reject()</slot> 
    165    <hints> 
    166     <hint type="sourcelabel"> 
    167      <x>266</x> 
    168      <y>183</y> 
    169     </hint> 
    170     <hint type="destinationlabel"> 
    171      <x>219</x> 
    172      <y>88</y> 
    173     </hint> 
    174    </hints> 
    175   </connection> 
    176   <connection> 
    177    <sender>removeButton</sender> 
    178    <signal>clicked()</signal> 
    179    <receiver>CategoryManagerUI</receiver> 
    180    <slot>accept()</slot> 
    181    <hints> 
    182     <hint type="sourcelabel"> 
    183      <x>66</x> 
    184      <y>183</y> 
    185     </hint> 
    186     <hint type="destinationlabel"> 
    187      <x>219</x> 
    188      <y>88</y> 
    189     </hint> 
    190    </hints> 
    191   </connection> 
    192  </connections> 
     168 <connections/> 
    193169</ui> 
  • src/sas/qtgui/Perspectives/Fitting/FittingWidget.py

    re4c475b7 r693aaf79  
    469469        self.options_widget.plot_signal.connect(self.onOptionsUpdate) 
    470470 
     471        # Communicator signal 
     472        self.communicate.updateModelCategoriesSignal.connect(self.onCategoriesChanged) 
     473 
    471474    def modelName(self): 
    472475        """ 
     
    19291932        #self.communicate.plotUpdateSignal.emit([residuals_plot]) 
    19301933 
     1934    def onCategoriesChanged(self): 
     1935            """ 
     1936            Reload the category/model comboboxes 
     1937            """ 
     1938            # Store the current combo indices 
     1939            current_cat = self.cbCategory.currentText() 
     1940            current_model = self.cbModel.currentText() 
     1941 
     1942            # reread the category file and repopulate the combo 
     1943            self.cbCategory.blockSignals(True) 
     1944            self.cbCategory.clear() 
     1945            self.readCategoryInfo() 
     1946            self.initializeCategoryCombo() 
     1947 
     1948            # Scroll back to the original index in Categories 
     1949            new_index = self.cbCategory.findText(current_cat) 
     1950            if new_index != -1: 
     1951                self.cbCategory.setCurrentIndex(new_index) 
     1952            self.cbCategory.blockSignals(False) 
     1953            # ...and in the Models 
     1954            self.cbModel.blockSignals(True) 
     1955            new_index = self.cbModel.findText(current_model) 
     1956            if new_index != -1: 
     1957                self.cbModel.setCurrentIndex(new_index) 
     1958            self.cbModel.blockSignals(False) 
     1959 
     1960            return 
     1961 
    19311962    def calcException(self, etype, value, tb): 
    19321963        """ 
  • src/sas/qtgui/Utilities/GuiUtils.py

    r63319b0 r693aaf79  
    236236    # Send result of Data Operation Utility panel to Data Explorer 
    237237    updateModelFromDataOperationPanelSignal = QtCore.pyqtSignal(QtGui.QStandardItem, dict) 
     238 
     239    # Notify about new categories/models from category manager 
     240    updateModelCategoriesSignal = QtCore.pyqtSignal() 
    238241 
    239242def updateModelItemWithPlot(item, update_data, name=""): 
Note: See TracChangeset for help on using the changeset viewer.