Repository: biolab/orange3

Issue:
Data Table: Index Error

##### Orange version
<!-- From menu _Help→About→Version_ or code `Orange.version.full_version` -->
3.7.dev

##### Expected behavior
Data Table reports no error.


##### Actual behavior
When reordering instances by some attribute, Data Table reports Index Error.


##### Steps to reproduce the behavior
File (iris) - Data Table, reorder by some attribute (say petal length), select some instances from the table, then click outside of the table (as if to deselect the selection).


##### Additional info (worksheets, data, screenshots, ...)
IndexError: arrays used as indices must be of integer (or boolean) type




Discussion:

Resolving patch:
diff --git a/Orange/widgets/utils/itemmodels.py b/Orange/widgets/utils/itemmodels.py
--- a/Orange/widgets/utils/itemmodels.py
+++ b/Orange/widgets/utils/itemmodels.py
@@ -118,7 +118,7 @@ def mapToSourceRows(self, rows):
 
         Parameters
         ----------
-        rows : int or list of int or numpy.ndarray or Ellipsis
+        rows : int or list of int or numpy.ndarray of dtype=int or Ellipsis
             View (sorted) rows.
 
         Returns
@@ -127,7 +127,10 @@ def mapToSourceRows(self, rows):
             Source rows matching input rows. If they are the same,
             simply input `rows` is returned.
         """
-        if self.__sortInd is not None:
+        # self.__sortInd[rows] fails if `rows` is an empty list or array
+        if self.__sortInd is not None \
+                and (isinstance(rows, (Integral, type(Ellipsis)))
+                     or len(rows)):
             new_rows = self.__sortInd[rows]
             if rows is Ellipsis:
                 new_rows.setflags(write=False)
@@ -139,7 +142,7 @@ def mapFromSourceRows(self, rows):
 
         Parameters
         ----------
-        rows : int or list of int or numpy.ndarray or Ellipsis
+        rows : int or list of int or numpy.ndarray of dtype=int or Ellipsis
             Source model rows.
 
         Returns
@@ -148,7 +151,10 @@ def mapFromSourceRows(self, rows):
             ModelIndex (sorted) rows matching input source rows.
             If they are the same, simply input `rows` is returned.
         """
-        if self.__sortIndInv is not None:
+        # self.__sortInd[rows] fails if `rows` is an empty list or array
+        if self.__sortIndInv is not None \
+                and (isinstance(rows, (Integral, type(Ellipsis)))
+                     or len(rows)):
             new_rows = self.__sortIndInv[rows]
             if rows is Ellipsis:
                 new_rows.setflags(write=False)


Test patch:
diff --git a/Orange/widgets/utils/tests/test_itemmodels.py b/Orange/widgets/utils/tests/test_itemmodels.py
--- a/Orange/widgets/utils/tests/test_itemmodels.py
+++ b/Orange/widgets/utils/tests/test_itemmodels.py
@@ -3,6 +3,8 @@
 
 from unittest import TestCase
 
+import numpy as np
+
 from AnyQt.QtCore import Qt
 
 from Orange.data import Domain, ContinuousVariable, DiscreteVariable
@@ -121,17 +123,31 @@ def test_other_roles(self):
 
 
 class TestAbstractSortTableModel(TestCase):
-    def setUp(self):
-        assert issubclass(PyTableModel, AbstractSortTableModel)
-        self.model = PyTableModel([[1, 4],
-                                   [2, 3]])
-
     def test_sorting(self):
-        self.model.sort(1, Qt.AscendingOrder)
-        self.assertSequenceEqual(self.model.mapToSourceRows(...).tolist(), [1, 0])
-
-        self.model.sort(1, Qt.DescendingOrder)
-        self.assertSequenceEqual(self.model.mapToSourceRows(...).tolist(), [0, 1])
+        assert issubclass(PyTableModel, AbstractSortTableModel)
+        model = PyTableModel([[1, 4],
+                              [2, 2],
+                              [3, 3]])
+        model.sort(1, Qt.AscendingOrder)
+        # mapToSourceRows
+        self.assertSequenceEqual(model.mapToSourceRows(...).tolist(), [1, 2, 0])
+        self.assertEqual(model.mapToSourceRows(1).tolist(), 2)
+        self.assertSequenceEqual(model.mapToSourceRows([1, 2]).tolist(), [2, 0])
+        self.assertSequenceEqual(model.mapToSourceRows([]), [])
+        self.assertSequenceEqual(model.mapToSourceRows(np.array([], dtype=int)).tolist(), [])
+        self.assertRaises(IndexError, model.mapToSourceRows, np.r_[0.])
+
+        # mapFromSourceRows
+        self.assertSequenceEqual(model.mapFromSourceRows(...).tolist(), [2, 0, 1])
+        self.assertEqual(model.mapFromSourceRows(1).tolist(), 0)
+        self.assertSequenceEqual(model.mapFromSourceRows([1, 2]).tolist(), [0, 1])
+        self.assertSequenceEqual(model.mapFromSourceRows([]), [])
+        self.assertSequenceEqual(model.mapFromSourceRows(np.array([], dtype=int)).tolist(), [])
+        self.assertRaises(IndexError, model.mapFromSourceRows, np.r_[0.])
+
+        model.sort(1, Qt.DescendingOrder)
+        self.assertSequenceEqual(model.mapToSourceRows(...).tolist(), [0, 2, 1])
+        self.assertSequenceEqual(model.mapFromSourceRows(...).tolist(), [0, 2, 1])
 
 
 class TestPyListModel(TestCase):