Ignore:
Timestamp:
04/03/12 13:20:57 (2 years ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
rebase_source:
44732d0009c3047fce35cff169e33dea403ddcdd
Message:

Added checks for valid indices in Qt's get set methods for PyListModel (return an invalid QVariant's instead of raising exceptions).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeWidgets/OWItemModels.py

    r10459 r10725  
    3030    object.blockSignals(blocked) 
    3131 
    32      
     32 
    3333class PyListModel(QAbstractListModel): 
    3434    """ A model for displaying python list like objects in Qt item view classes 
    3535    """ 
    3636    MIME_TYPES = ["application/x-Orange-PyListModelData"] 
    37      
     37 
    3838    def __init__(self, iterable=[], parent=None, 
    3939                 flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled, 
     
    4545        self._flags = flags 
    4646        self.list_item_role = list_item_role 
    47          
     47 
    4848        self._supportedDropActions = supportedDropActions 
    4949        self.extend(iterable) 
    50          
     50 
     51    def _is_index_valid_for(self, index, list_like): 
     52        if isinstance(index, QModelIndex) and index.isValid() : 
     53            row, column = index.row(), index.column() 
     54            return row < len(list_like) and row >= 0 and column == 0 
     55        elif isinstance(index, int): 
     56            return index < len(list_like) and index > -len(self) 
     57        else: 
     58            return False 
     59 
    5160    def wrap(self, list): 
    5261        """ Wrap the list with this model. All changes to the model 
     
    5665        self._other_data = [_store() for _ in list] 
    5766        self.reset() 
    58      
     67 
    5968    def index(self, row, column=0, parent=QModelIndex()): 
    60         return QAbstractListModel.createIndex(self, row, column, parent) 
    61      
     69        if self._is_index_valid_for(row, self) and column == 0: 
     70            return QAbstractListModel.createIndex(self, row, column, parent) 
     71        else: 
     72            return QModelIndex() 
     73 
    6274    def headerData(self, section, orientation, role=Qt.DisplayRole): 
    6375        if role == Qt.DisplayRole: 
    6476            return QVariant(str(section)) 
    65      
     77 
    6678    def rowCount(self, parent=QModelIndex()): 
    6779        return 0 if parent.isValid() else len(self) 
    68      
     80 
    6981    def columnCount(self, parent=QModelIndex()): 
    7082        return 0 if parent.isValid() else 1 
    71      
     83 
    7284    def data(self, index, role=Qt.DisplayRole): 
    7385        row = index.row() 
    74         if role in [self.list_item_role, Qt.EditRole]: 
    75             return QVariant(self[index.row()]) 
    76         else: 
    77             return QVariant(self._other_data[index.row()].get(role, QVariant())) 
    78      
     86        if role in [self.list_item_role, Qt.EditRole] \ 
     87                and self._is_index_valid_for(index, self): 
     88            return QVariant(self[row]) 
     89        elif self._is_index_valid_for(row, self._other_data): 
     90            return QVariant(self._other_data[row].get(role, QVariant())) 
     91        else: 
     92            return QVariant() 
     93 
    7994    def itemData(self, index): 
    8095        map = QAbstractListModel.itemData(self, index) 
    81         for key, value in self._other_data[index.row()].items(): 
     96        if self._is_index_valid_for(index, self._other_data): 
     97            items = self._other_data[index.row()].items() 
     98        else: 
     99            items = [] 
     100 
     101        for key, value in items: 
    82102            map[key] = QVariant(value) 
     103 
    83104        return map 
    84      
     105 
    85106    def parent(self, index=QModelIndex()): 
    86107        return QModelIndex() 
    87      
     108 
    88109    def setData(self, index, value, role=Qt.EditRole): 
    89         if role == Qt.EditRole: 
     110        if role == Qt.EditRole and self._is_index_valid_for(index, self): 
    90111            obj = value.toPyObject() 
    91             self[index.row()] = obj 
    92         else: 
     112            self[index.row()] = obj # Will emit proper dataChanged signal 
     113            return True 
     114        elif self._is_index_valid_for(index, self._other_data): 
    93115            self._other_data[index.row()][role] = value 
    94116            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index) 
    95         return True 
    96          
     117            return True 
     118        else: 
     119            return False 
     120 
    97121    def setItemData(self, index, data): 
    98122        data = dict(data) 
    99123        with signal_blocking(self): 
    100124            for role, value in data.items(): 
    101                 if role == Qt.EditRole: 
     125                if role == Qt.EditRole and \ 
     126                        self._is_index_valid_for(index, self): 
    102127                    self[index.row()] = value.toPyObject() 
    103                 else: 
     128                elif self._is_index_valid_for(index, self._other_data): 
    104129                    self._other_data[index.row()][role] = value 
    105                      
     130 
    106131        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index) 
    107132        return True 
    108 #        return QAbstractListModel.setItemData(self, index, data) 
    109      
     133 
    110134    def flags(self, index): 
    111         if index.isValid() and index.row() < len(self._other_data): 
     135        if self._is_index_valid_for(index, self._other_data): 
    112136            return self._other_data[index.row()].get("flags", self._flags) 
    113137        else: 
    114138            return self._flags | Qt.ItemIsDropEnabled 
    115          
     139 
    116140    def insertRows(self, row, count, parent=QModelIndex()): 
    117141        """ Insert ``count`` rows at ``row``, the list fill be filled  
    118142        with ``None``  
    119143        """ 
    120 #        print "Insert", row, count 
    121144        if not parent.isValid(): 
    122145            self.__setslice__(row, row, [None] * count) 
     
    124147        else: 
    125148            return False 
    126          
     149 
    127150    def removeRows(self, row, count, parent=QModelIndex()): 
     151        """Remove ``count`` rows starting at ``row`` 
    128152        """ 
    129         """ 
    130 #        print "Remove", row, count 
    131153        if not parent.isValid(): 
    132154            self.__delslice__(row, row + count) 
     
    134156        else: 
    135157            return False 
    136      
     158 
    137159    def extend(self, iterable): 
    138160        list_ = list(iterable) 
     
    141163        self._other_data.extend([_store() for _ in list_]) 
    142164        self.endInsertRows() 
    143      
     165 
    144166    def append(self, item): 
    145167        self.extend([item]) 
    146          
     168 
    147169    def insert(self, i, val): 
    148170        self.beginInsertRows(QModelIndex(), i, i) 
     
    150172        self._other_data.insert(i, _store()) 
    151173        self.endInsertRows() 
    152          
     174 
    153175    def remove(self, val): 
    154176        i = self._list.index(val) 
    155177        self.__delitem__(i) 
    156          
     178 
    157179    def pop(self, i): 
    158180        item = self._list[i] 
    159181        self.__delitem__(i) 
    160182        return item 
    161      
     183 
    162184    def __len__(self): 
    163185        return len(self._list) 
    164      
     186 
    165187    def __iter__(self): 
    166188        return iter(self._list) 
    167      
     189 
    168190    def __getitem__(self, i): 
    169191        return self._list[i] 
    170      
     192 
    171193    def __getslice__(self, i, j): 
    172194        return self._list[i:j] 
    173          
     195 
    174196    def __add__(self, iterable): 
    175         # Does not preserve flags or other data. 
    176         return PyListModel(self._list + iterable, self.parent()) 
    177      
     197        new_list = PyListModel(list(self._list),  
     198                           self.parent(), 
     199                           flags=self._flags, 
     200                           list_item_role=self.list_item_role, 
     201                           supportedDropActions=self.supportedDropActions() 
     202                           ) 
     203        new_list._other_data = list(self._other_data) 
     204        new_list.extend(iterable) 
     205        return new_list 
     206 
    178207    def __iadd__(self, iterable): 
    179208        self.extend(iterable) 
    180          
     209 
    181210    def __delitem__(self, i): 
    182211        self.beginRemoveRows(QModelIndex(), i, i) 
     
    184213        del self._other_data[i] 
    185214        self.endRemoveRows() 
    186          
     215 
    187216    def __delslice__(self, i, j): 
    188217        if j > i: 
     
    191220            del self._other_data[i:j] 
    192221            self.endRemoveRows() 
    193          
     222 
    194223    def __setitem__(self, i, value): 
    195224        self._list[i] = value 
    196225        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(i), self.index(i)) 
    197          
     226 
    198227    def __setslice__(self, i, j, iterable): 
    199228        self.__delslice__(i, j) 
     
    204233            self._other_data[i:i] = [_store() for _ in all] 
    205234            self.endInsertRows() 
    206          
     235 
    207236    def reverse(self): 
    208237        self._list.reverse() 
    209238        self._other_data.reverse() 
    210239        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(0), self.index(len(self) -1)) 
    211          
     240 
    212241    def sort(self, *args, **kwargs): 
    213242        indices = _argsort(self._list, *args, **kwargs) 
     
    217246            self._list[i] = new_l 
    218247            self._other_data[i] = new_o 
    219 #        self._list.sort(*args, **kwargs) 
    220248        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(0), self.index(len(self) -1)) 
    221          
     249 
    222250    def __repr__(self): 
    223251        return "PyListModel(%s)" % repr(self._list) 
    224      
     252 
    225253    def __nonzero__(self): 
    226254        return len(self) != 0 
    227      
     255 
    228256    #for Python 3000 
    229257    def __bool__(self): 
    230258        return len(self) != 0 
    231      
     259 
    232260    def emitDataChanged(self, indexList): 
    233261        if isinstance(indexList, int): 
    234262            indexList = [indexList] 
     263 
     264        #TODO: group indexes into ranges 
    235265        for ind in indexList: 
    236266            self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(ind), self.index(ind)) 
    237              
     267 
    238268    ########### 
    239269    # Drag/drop 
    240270    ########### 
    241      
     271 
    242272    def supportedDropActions(self): 
    243273        return self._supportedDropActions 
    244      
     274 
    245275    def decode_qt_data(self, data): 
    246276        """ Decode internal Qt 'application/x-qabstractitemmodeldatalist' 
     
    260290            items.append((row, column, item)) 
    261291        return items 
    262      
     292 
    263293    def mimeTypes(self): 
    264294        return self.MIME_TYPES + list(QAbstractListModel.mimeTypes(self)) 
    265      
     295 
    266296    def mimeData(self, indexlist): 
    267297        if len(indexlist) <= 0: 
    268298            return None 
    269          
     299 
    270300        items = [self[i.row()] for i in indexlist] 
    271301        mime = QAbstractListModel.mimeData(self, indexlist) 
     
    274304        mime._items = items 
    275305        return mime 
    276      
     306 
    277307    def dropMimeData(self, mime, action, row, column, parent): 
    278308        if action == Qt.IgnoreAction: 
    279309            return True 
    280          
     310 
    281311        if not mime.hasFormat(self.MIME_TYPE): 
    282312            return False 
    283          
     313 
    284314        if hasattr(mime, "_vars"): 
    285315            vars = mime._vars 
     
    287317            desc = str(mime.data(self.MIME_TYPE)) 
    288318            vars = cPickle.loads(desc) 
    289          
     319 
    290320        return QAbstractListModel.dropMimeData(self, mime, action, row, column, parent) 
    291      
    292      
     321 
     322 
    293323import OWGUI 
    294324import orange 
     
    297327 
    298328class VariableListModel(PyListModel): 
    299      
     329 
    300330    MIME_TYPE = "application/x-Orange-VariableList" 
    301      
     331 
    302332    def data(self, index, role=Qt.DisplayRole): 
    303         i = index.row() 
    304         var = self[i] 
    305         if role == Qt.DisplayRole: 
    306             return QVariant(var.name) 
    307         elif role == Qt.DecorationRole: 
    308             return QVariant(OWGUI.getAttributeIcons().get(var.varType, -1)) 
    309         elif role == Qt.ToolTipRole: 
    310             return QVariant(self.variable_tooltip(var)) 
    311         else: 
    312             return PyListModel.data(self, index, role) 
    313          
     333        if self._is_index_valid_for(index, self): 
     334            i = index.row() 
     335            var = self[i] 
     336            if role == Qt.DisplayRole: 
     337                return QVariant(var.name) 
     338            elif role == Qt.DecorationRole: 
     339                return QVariant(OWGUI.getAttributeIcons().get(var.varType, -1)) 
     340            elif role == Qt.ToolTipRole: 
     341                return QVariant(self.variable_tooltip(var)) 
     342            else: 
     343                return PyListModel.data(self, index, role) 
     344        else: 
     345            return QVariant() 
     346 
    314347    def variable_tooltip(self, var): 
    315348        if isinstance(var, Orange.feature.Discrete): 
     
    319352        elif isinstance(var, Orange.feature.String): 
    320353            return self.string_variable_tooltip(var) 
    321          
     354 
    322355    def variable_labels_tooltip(self, var): 
    323356        text = "" 
     
    329362            text += "<br/>".join(labels) 
    330363        return text 
    331              
     364 
    332365    def discrete_variable_tooltip(self, var): 
    333366        text = "<b>%s</b><br/>Discrete with %i values: " % (safe_text(var.name), len(var.values)) 
     
    335368        text += self.variable_labels_tooltip(var) 
    336369        return text 
    337              
     370 
    338371    def continuous_variable_toltip(self, var): 
    339372        text = "<b>%s</b><br/>Continuous" % safe_text(var.name) 
    340373        text += self.variable_labels_tooltip(var) 
    341374        return text 
    342      
     375 
    343376    def string_variable_tooltip(self, var): 
    344377        text = "<b>%s</b><br/>String" % safe_text(var.name) 
    345378        text += self.variable_labels_tooltip(var) 
    346379        return text 
    347      
     380 
    348381    def python_variable_tooltip(self, var): 
    349382        text = "<b>%s</b><br/>Python" % safe_text(var.name) 
    350383        text += self.variable_labels_tooltip(var) 
    351384        return text 
    352      
     385 
    353386_html_replace = [("<", "&lt;"), (">", "&gt;")] 
    354387 
Note: See TracChangeset for help on using the changeset viewer.