We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
BUG: UI annoyance - Predictions Widget measures grid squashing
This is not a classic bug, but rather big visual annoyance: Predictions Widget squashes the grid with measures every time I retrain anything that sends data to it - like NN or LR. It seems that it chooses column widths based on the header text width and ignores the text in the cells below. Once I click on the model column heading or any other column to sort it, it unsquashes it - that is, it considers cells text width as well. Can we have that not squashed every time retraining is requested? It seems that it does remember its column order although sort column does not have that triangle shown after being squashed, thus I do not know which column was used for sorting in the first place. Thank you n advance for fixing this.

[1] Sqaushed
![image](https://user-images.githubusercontent.com/32625394/152621624-e29d79d4-2f91-4963-b2ba-1efb12a4e4b9.png)

[2] Unsqaushed
![image](https://user-images.githubusercontent.com/32625394/152621638-728badab-7b62-46bc-8568-a697a171ef7a.png)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Orange/widgets/evaluate/utils.py`
Content:
```
1 import warnings
2 from functools import partial
3 from itertools import chain
4 
5 import numpy as np
6 
7 from AnyQt.QtWidgets import QHeaderView, QStyledItemDelegate, QMenu, \
8     QApplication
9 from AnyQt.QtGui import QStandardItemModel, QStandardItem, QClipboard
10 from AnyQt.QtCore import Qt, QSize, QObject, pyqtSignal as Signal, \
11     QSortFilterProxyModel
12 from sklearn.exceptions import UndefinedMetricWarning
13 
14 from Orange.data import Variable, DiscreteVariable, ContinuousVariable
15 from Orange.evaluation import scoring
16 from Orange.widgets import gui
17 from Orange.widgets.utils.tableview import table_selection_to_mime_data
18 from Orange.widgets.gui import OWComponent
19 from Orange.widgets.settings import Setting
20 
21 
22 def check_results_adequacy(results, error_group, check_nan=True):
23     error_group.add_message("invalid_results")
24     error_group.invalid_results.clear()
25 
26     def anynan(a):
27         return np.any(np.isnan(a))
28 
29     if results is None:
30         return None
31     if results.data is None:
32         error_group.invalid_results(
33             "Results do not include information on test data.")
34     elif not results.data.domain.has_discrete_class:
35         error_group.invalid_results(
36             "Categorical target variable is required.")
37     elif not results.actual.size:
38         error_group.invalid_results(
39             "Empty result on input. Nothing to display.")
40     elif check_nan and (anynan(results.actual) or
41                         anynan(results.predicted) or
42                         (results.probabilities is not None and
43                          anynan(results.probabilities))):
44         error_group.invalid_results(
45             "Results contain invalid values.")
46     else:
47         return results
48 
49 
50 def results_for_preview(data_name=""):
51     from Orange.data import Table
52     from Orange.evaluation import CrossValidation
53     from Orange.classification import \
54         LogisticRegressionLearner, SVMLearner, NuSVMLearner
55 
56     data = Table(data_name or "heart_disease")
57     results = CrossValidation(
58         data,
59         [LogisticRegressionLearner(penalty="l2"),
60          LogisticRegressionLearner(penalty="l1"),
61          SVMLearner(probability=True),
62          NuSVMLearner(probability=True)
63         ],
64         store_data=True
65     )
66     results.learner_names = ["LR l2", "LR l1", "SVM", "Nu SVM"]
67     return results
68 
69 
70 BUILTIN_SCORERS_ORDER = {
71     DiscreteVariable: ("AUC", "CA", "F1", "Precision", "Recall"),
72     ContinuousVariable: ("MSE", "RMSE", "MAE", "R2")}
73 
74 
75 def learner_name(learner):
76     """Return the value of `learner.name` if it exists, or the learner's type
77     name otherwise"""
78     return getattr(learner, "name", type(learner).__name__)
79 
80 
81 def usable_scorers(target: Variable):
82     order = {name: i
83              for i, name in enumerate(BUILTIN_SCORERS_ORDER[type(target)])}
84     # 'abstract' is retrieved from __dict__ to avoid inheriting
85     usable = (cls for cls in scoring.Score.registry.values()
86               if cls.is_scalar and not cls.__dict__.get("abstract")
87               and isinstance(target, cls.class_types))
88     return sorted(usable, key=lambda cls: order.get(cls.name, 99))
89 
90 
91 def scorer_caller(scorer, ovr_results, target=None):
92     def thunked():
93         with warnings.catch_warnings():
94             # F-score and Precision return 0 for labels with no predicted
95             # samples. We're OK with that.
96             warnings.filterwarnings(
97                 "ignore", "((F-score|Precision)) is ill-defined.*",
98                 UndefinedMetricWarning)
99             if scorer.is_binary:
100                 return scorer(ovr_results, target=target, average='weighted')
101             else:
102                 return scorer(ovr_results)
103 
104     return thunked
105 
106 
107 class ScoreModel(QSortFilterProxyModel):
108     def lessThan(self, left, right):
109         def is_bad(x):
110             return not isinstance(x, (int, float, str)) \
111                    or isinstance(x, float) and np.isnan(x)
112 
113         left = left.data()
114         right = right.data()
115         is_ascending = self.sortOrder() == Qt.AscendingOrder
116 
117         # bad entries go below; if both are bad, left remains above
118         if is_bad(left) or is_bad(right):
119             return is_bad(right) == is_ascending
120 
121         # for data of different types, numbers are at the top
122         if type(left) is not type(right):
123             return isinstance(left, float) == is_ascending
124 
125         # case insensitive comparison for strings
126         if isinstance(left, str):
127             return left.upper() < right.upper()
128 
129         # otherwise, compare numbers
130         return left < right
131 
132 
133 class ScoreTable(OWComponent, QObject):
134     shown_scores = \
135         Setting(set(chain(*BUILTIN_SCORERS_ORDER.values())))
136 
137     shownScoresChanged = Signal()
138 
139     class ItemDelegate(QStyledItemDelegate):
140         def sizeHint(self, *args):
141             size = super().sizeHint(*args)
142             return QSize(size.width(), size.height() + 6)
143 
144         def displayText(self, value, locale):
145             if isinstance(value, float):
146                 return f"{value:.3f}"
147             else:
148                 return super().displayText(value, locale)
149 
150     def __init__(self, master):
151         QObject.__init__(self)
152         OWComponent.__init__(self, master)
153 
154         self.view = gui.TableView(
155             wordWrap=True, editTriggers=gui.TableView.NoEditTriggers
156         )
157         header = self.view.horizontalHeader()
158         header.setSectionResizeMode(QHeaderView.ResizeToContents)
159         header.setDefaultAlignment(Qt.AlignCenter)
160         header.setStretchLastSection(False)
161         header.setContextMenuPolicy(Qt.CustomContextMenu)
162         header.customContextMenuRequested.connect(self.show_column_chooser)
163 
164         self.model = QStandardItemModel(master)
165         self.model.setHorizontalHeaderLabels(["Method"])
166         self.sorted_model = ScoreModel()
167         self.sorted_model.setSourceModel(self.model)
168         self.view.setModel(self.sorted_model)
169         self.view.setItemDelegate(self.ItemDelegate())
170 
171     def _column_names(self):
172         return (self.model.horizontalHeaderItem(section).data(Qt.DisplayRole)
173                 for section in range(1, self.model.columnCount()))
174 
175     def show_column_chooser(self, pos):
176         # pylint doesn't know that self.shown_scores is a set, not a Setting
177         # pylint: disable=unsupported-membership-test
178         def update(col_name, checked):
179             if checked:
180                 self.shown_scores.add(col_name)
181             else:
182                 self.shown_scores.remove(col_name)
183             self._update_shown_columns()
184 
185         menu = QMenu()
186         header = self.view.horizontalHeader()
187         for col_name in self._column_names():
188             action = menu.addAction(col_name)
189             action.setCheckable(True)
190             action.setChecked(col_name in self.shown_scores)
191             action.triggered.connect(partial(update, col_name))
192         menu.exec(header.mapToGlobal(pos))
193 
194     def _update_shown_columns(self):
195         # pylint doesn't know that self.shown_scores is a set, not a Setting
196         # pylint: disable=unsupported-membership-test
197         header = self.view.horizontalHeader()
198         for section, col_name in enumerate(self._column_names(), start=1):
199             header.setSectionHidden(section, col_name not in self.shown_scores)
200         self.view.resizeColumnsToContents()
201         self.shownScoresChanged.emit()
202 
203     def update_header(self, scorers):
204         # Set the correct horizontal header labels on the results_model.
205         self.model.setColumnCount(3 + len(scorers))
206         self.model.setHorizontalHeaderItem(0, QStandardItem("Model"))
207         self.model.setHorizontalHeaderItem(1, QStandardItem("Train time [s]"))
208         self.model.setHorizontalHeaderItem(2, QStandardItem("Test time [s]"))
209         for col, score in enumerate(scorers, start=3):
210             item = QStandardItem(score.name)
211             item.setToolTip(score.long_name)
212             self.model.setHorizontalHeaderItem(col, item)
213         self._update_shown_columns()
214 
215     def copy_selection_to_clipboard(self):
216         mime = table_selection_to_mime_data(self.view)
217         QApplication.clipboard().setMimeData(
218             mime, QClipboard.Clipboard
219         )
220
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Orange/widgets/evaluate/utils.py b/Orange/widgets/evaluate/utils.py
--- a/Orange/widgets/evaluate/utils.py
+++ b/Orange/widgets/evaluate/utils.py
@@ -194,10 +194,10 @@
     def _update_shown_columns(self):
         # pylint doesn't know that self.shown_scores is a set, not a Setting
         # pylint: disable=unsupported-membership-test
+        self.view.resizeColumnsToContents()
         header = self.view.horizontalHeader()
         for section, col_name in enumerate(self._column_names(), start=1):
             header.setSectionHidden(section, col_name not in self.shown_scores)
-        self.view.resizeColumnsToContents()
         self.shownScoresChanged.emit()
 
     def update_header(self, scorers):

{"golden_diff": "diff --git a/Orange/widgets/evaluate/utils.py b/Orange/widgets/evaluate/utils.py\n--- a/Orange/widgets/evaluate/utils.py\n+++ b/Orange/widgets/evaluate/utils.py\n@@ -194,10 +194,10 @@\n     def _update_shown_columns(self):\n         # pylint doesn't know that self.shown_scores is a set, not a Setting\n         # pylint: disable=unsupported-membership-test\n+        self.view.resizeColumnsToContents()\n         header = self.view.horizontalHeader()\n         for section, col_name in enumerate(self._column_names(), start=1):\n             header.setSectionHidden(section, col_name not in self.shown_scores)\n-        self.view.resizeColumnsToContents()\n         self.shownScoresChanged.emit()\n \n     def update_header(self, scorers):\n", "issue": "BUG: UI annoyance - Predictions Widget measures grid squashing\nThis is not a classic bug, but rather big visual annoyance: Predictions Widget squashes the grid with measures every time I retrain anything that sends data to it - like NN or LR. It seems that it chooses column widths based on the header text width and ignores the text in the cells below. Once I click on the model column heading or any other column to sort it, it unsquashes it - that is, it considers cells text width as well. Can we have that not squashed every time retraining is requested? It seems that it does remember its column order although sort column does not have that triangle shown after being squashed, thus I do not know which column was used for sorting in the first place. Thank you n advance for fixing this.\r\n\r\n[1] Sqaushed\r\n![image](https://user-images.githubusercontent.com/32625394/152621624-e29d79d4-2f91-4963-b2ba-1efb12a4e4b9.png)\r\n\r\n[2] Unsqaushed\r\n![image](https://user-images.githubusercontent.com/32625394/152621638-728badab-7b62-46bc-8568-a697a171ef7a.png)\r\n\n", "before_files": [{"content": "import warnings\nfrom functools import partial\nfrom itertools import chain\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import QHeaderView, QStyledItemDelegate, QMenu, \\\n    QApplication\nfrom AnyQt.QtGui import QStandardItemModel, QStandardItem, QClipboard\nfrom AnyQt.QtCore import Qt, QSize, QObject, pyqtSignal as Signal, \\\n    QSortFilterProxyModel\nfrom sklearn.exceptions import UndefinedMetricWarning\n\nfrom Orange.data import Variable, DiscreteVariable, ContinuousVariable\nfrom Orange.evaluation import scoring\nfrom Orange.widgets import gui\nfrom Orange.widgets.utils.tableview import table_selection_to_mime_data\nfrom Orange.widgets.gui import OWComponent\nfrom Orange.widgets.settings import Setting\n\n\ndef check_results_adequacy(results, error_group, check_nan=True):\n    error_group.add_message(\"invalid_results\")\n    error_group.invalid_results.clear()\n\n    def anynan(a):\n        return np.any(np.isnan(a))\n\n    if results is None:\n        return None\n    if results.data is None:\n        error_group.invalid_results(\n            \"Results do not include information on test data.\")\n    elif not results.data.domain.has_discrete_class:\n        error_group.invalid_results(\n            \"Categorical target variable is required.\")\n    elif not results.actual.size:\n        error_group.invalid_results(\n            \"Empty result on input. Nothing to display.\")\n    elif check_nan and (anynan(results.actual) or\n                        anynan(results.predicted) or\n                        (results.probabilities is not None and\n                         anynan(results.probabilities))):\n        error_group.invalid_results(\n            \"Results contain invalid values.\")\n    else:\n        return results\n\n\ndef results_for_preview(data_name=\"\"):\n    from Orange.data import Table\n    from Orange.evaluation import CrossValidation\n    from Orange.classification import \\\n        LogisticRegressionLearner, SVMLearner, NuSVMLearner\n\n    data = Table(data_name or \"heart_disease\")\n    results = CrossValidation(\n        data,\n        [LogisticRegressionLearner(penalty=\"l2\"),\n         LogisticRegressionLearner(penalty=\"l1\"),\n         SVMLearner(probability=True),\n         NuSVMLearner(probability=True)\n        ],\n        store_data=True\n    )\n    results.learner_names = [\"LR l2\", \"LR l1\", \"SVM\", \"Nu SVM\"]\n    return results\n\n\nBUILTIN_SCORERS_ORDER = {\n    DiscreteVariable: (\"AUC\", \"CA\", \"F1\", \"Precision\", \"Recall\"),\n    ContinuousVariable: (\"MSE\", \"RMSE\", \"MAE\", \"R2\")}\n\n\ndef learner_name(learner):\n    \"\"\"Return the value of `learner.name` if it exists, or the learner's type\n    name otherwise\"\"\"\n    return getattr(learner, \"name\", type(learner).__name__)\n\n\ndef usable_scorers(target: Variable):\n    order = {name: i\n             for i, name in enumerate(BUILTIN_SCORERS_ORDER[type(target)])}\n    # 'abstract' is retrieved from __dict__ to avoid inheriting\n    usable = (cls for cls in scoring.Score.registry.values()\n              if cls.is_scalar and not cls.__dict__.get(\"abstract\")\n              and isinstance(target, cls.class_types))\n    return sorted(usable, key=lambda cls: order.get(cls.name, 99))\n\n\ndef scorer_caller(scorer, ovr_results, target=None):\n    def thunked():\n        with warnings.catch_warnings():\n            # F-score and Precision return 0 for labels with no predicted\n            # samples. We're OK with that.\n            warnings.filterwarnings(\n                \"ignore\", \"((F-score|Precision)) is ill-defined.*\",\n                UndefinedMetricWarning)\n            if scorer.is_binary:\n                return scorer(ovr_results, target=target, average='weighted')\n            else:\n                return scorer(ovr_results)\n\n    return thunked\n\n\nclass ScoreModel(QSortFilterProxyModel):\n    def lessThan(self, left, right):\n        def is_bad(x):\n            return not isinstance(x, (int, float, str)) \\\n                   or isinstance(x, float) and np.isnan(x)\n\n        left = left.data()\n        right = right.data()\n        is_ascending = self.sortOrder() == Qt.AscendingOrder\n\n        # bad entries go below; if both are bad, left remains above\n        if is_bad(left) or is_bad(right):\n            return is_bad(right) == is_ascending\n\n        # for data of different types, numbers are at the top\n        if type(left) is not type(right):\n            return isinstance(left, float) == is_ascending\n\n        # case insensitive comparison for strings\n        if isinstance(left, str):\n            return left.upper() < right.upper()\n\n        # otherwise, compare numbers\n        return left < right\n\n\nclass ScoreTable(OWComponent, QObject):\n    shown_scores = \\\n        Setting(set(chain(*BUILTIN_SCORERS_ORDER.values())))\n\n    shownScoresChanged = Signal()\n\n    class ItemDelegate(QStyledItemDelegate):\n        def sizeHint(self, *args):\n            size = super().sizeHint(*args)\n            return QSize(size.width(), size.height() + 6)\n\n        def displayText(self, value, locale):\n            if isinstance(value, float):\n                return f\"{value:.3f}\"\n            else:\n                return super().displayText(value, locale)\n\n    def __init__(self, master):\n        QObject.__init__(self)\n        OWComponent.__init__(self, master)\n\n        self.view = gui.TableView(\n            wordWrap=True, editTriggers=gui.TableView.NoEditTriggers\n        )\n        header = self.view.horizontalHeader()\n        header.setSectionResizeMode(QHeaderView.ResizeToContents)\n        header.setDefaultAlignment(Qt.AlignCenter)\n        header.setStretchLastSection(False)\n        header.setContextMenuPolicy(Qt.CustomContextMenu)\n        header.customContextMenuRequested.connect(self.show_column_chooser)\n\n        self.model = QStandardItemModel(master)\n        self.model.setHorizontalHeaderLabels([\"Method\"])\n        self.sorted_model = ScoreModel()\n        self.sorted_model.setSourceModel(self.model)\n        self.view.setModel(self.sorted_model)\n        self.view.setItemDelegate(self.ItemDelegate())\n\n    def _column_names(self):\n        return (self.model.horizontalHeaderItem(section).data(Qt.DisplayRole)\n                for section in range(1, self.model.columnCount()))\n\n    def show_column_chooser(self, pos):\n        # pylint doesn't know that self.shown_scores is a set, not a Setting\n        # pylint: disable=unsupported-membership-test\n        def update(col_name, checked):\n            if checked:\n                self.shown_scores.add(col_name)\n            else:\n                self.shown_scores.remove(col_name)\n            self._update_shown_columns()\n\n        menu = QMenu()\n        header = self.view.horizontalHeader()\n        for col_name in self._column_names():\n            action = menu.addAction(col_name)\n            action.setCheckable(True)\n            action.setChecked(col_name in self.shown_scores)\n            action.triggered.connect(partial(update, col_name))\n        menu.exec(header.mapToGlobal(pos))\n\n    def _update_shown_columns(self):\n        # pylint doesn't know that self.shown_scores is a set, not a Setting\n        # pylint: disable=unsupported-membership-test\n        header = self.view.horizontalHeader()\n        for section, col_name in enumerate(self._column_names(), start=1):\n            header.setSectionHidden(section, col_name not in self.shown_scores)\n        self.view.resizeColumnsToContents()\n        self.shownScoresChanged.emit()\n\n    def update_header(self, scorers):\n        # Set the correct horizontal header labels on the results_model.\n        self.model.setColumnCount(3 + len(scorers))\n        self.model.setHorizontalHeaderItem(0, QStandardItem(\"Model\"))\n        self.model.setHorizontalHeaderItem(1, QStandardItem(\"Train time [s]\"))\n        self.model.setHorizontalHeaderItem(2, QStandardItem(\"Test time [s]\"))\n        for col, score in enumerate(scorers, start=3):\n            item = QStandardItem(score.name)\n            item.setToolTip(score.long_name)\n            self.model.setHorizontalHeaderItem(col, item)\n        self._update_shown_columns()\n\n    def copy_selection_to_clipboard(self):\n        mime = table_selection_to_mime_data(self.view)\n        QApplication.clipboard().setMimeData(\n            mime, QClipboard.Clipboard\n        )\n", "path": "Orange/widgets/evaluate/utils.py"}], "after_files": [{"content": "import warnings\nfrom functools import partial\nfrom itertools import chain\n\nimport numpy as np\n\nfrom AnyQt.QtWidgets import QHeaderView, QStyledItemDelegate, QMenu, \\\n    QApplication\nfrom AnyQt.QtGui import QStandardItemModel, QStandardItem, QClipboard\nfrom AnyQt.QtCore import Qt, QSize, QObject, pyqtSignal as Signal, \\\n    QSortFilterProxyModel\nfrom sklearn.exceptions import UndefinedMetricWarning\n\nfrom Orange.data import Variable, DiscreteVariable, ContinuousVariable\nfrom Orange.evaluation import scoring\nfrom Orange.widgets import gui\nfrom Orange.widgets.utils.tableview import table_selection_to_mime_data\nfrom Orange.widgets.gui import OWComponent\nfrom Orange.widgets.settings import Setting\n\n\ndef check_results_adequacy(results, error_group, check_nan=True):\n    error_group.add_message(\"invalid_results\")\n    error_group.invalid_results.clear()\n\n    def anynan(a):\n        return np.any(np.isnan(a))\n\n    if results is None:\n        return None\n    if results.data is None:\n        error_group.invalid_results(\n            \"Results do not include information on test data.\")\n    elif not results.data.domain.has_discrete_class:\n        error_group.invalid_results(\n            \"Categorical target variable is required.\")\n    elif not results.actual.size:\n        error_group.invalid_results(\n            \"Empty result on input. Nothing to display.\")\n    elif check_nan and (anynan(results.actual) or\n                        anynan(results.predicted) or\n                        (results.probabilities is not None and\n                         anynan(results.probabilities))):\n        error_group.invalid_results(\n            \"Results contain invalid values.\")\n    else:\n        return results\n\n\ndef results_for_preview(data_name=\"\"):\n    from Orange.data import Table\n    from Orange.evaluation import CrossValidation\n    from Orange.classification import \\\n        LogisticRegressionLearner, SVMLearner, NuSVMLearner\n\n    data = Table(data_name or \"heart_disease\")\n    results = CrossValidation(\n        data,\n        [LogisticRegressionLearner(penalty=\"l2\"),\n         LogisticRegressionLearner(penalty=\"l1\"),\n         SVMLearner(probability=True),\n         NuSVMLearner(probability=True)\n        ],\n        store_data=True\n    )\n    results.learner_names = [\"LR l2\", \"LR l1\", \"SVM\", \"Nu SVM\"]\n    return results\n\n\nBUILTIN_SCORERS_ORDER = {\n    DiscreteVariable: (\"AUC\", \"CA\", \"F1\", \"Precision\", \"Recall\"),\n    ContinuousVariable: (\"MSE\", \"RMSE\", \"MAE\", \"R2\")}\n\n\ndef learner_name(learner):\n    \"\"\"Return the value of `learner.name` if it exists, or the learner's type\n    name otherwise\"\"\"\n    return getattr(learner, \"name\", type(learner).__name__)\n\n\ndef usable_scorers(target: Variable):\n    order = {name: i\n             for i, name in enumerate(BUILTIN_SCORERS_ORDER[type(target)])}\n    # 'abstract' is retrieved from __dict__ to avoid inheriting\n    usable = (cls for cls in scoring.Score.registry.values()\n              if cls.is_scalar and not cls.__dict__.get(\"abstract\")\n              and isinstance(target, cls.class_types))\n    return sorted(usable, key=lambda cls: order.get(cls.name, 99))\n\n\ndef scorer_caller(scorer, ovr_results, target=None):\n    def thunked():\n        with warnings.catch_warnings():\n            # F-score and Precision return 0 for labels with no predicted\n            # samples. We're OK with that.\n            warnings.filterwarnings(\n                \"ignore\", \"((F-score|Precision)) is ill-defined.*\",\n                UndefinedMetricWarning)\n            if scorer.is_binary:\n                return scorer(ovr_results, target=target, average='weighted')\n            else:\n                return scorer(ovr_results)\n\n    return thunked\n\n\nclass ScoreModel(QSortFilterProxyModel):\n    def lessThan(self, left, right):\n        def is_bad(x):\n            return not isinstance(x, (int, float, str)) \\\n                   or isinstance(x, float) and np.isnan(x)\n\n        left = left.data()\n        right = right.data()\n        is_ascending = self.sortOrder() == Qt.AscendingOrder\n\n        # bad entries go below; if both are bad, left remains above\n        if is_bad(left) or is_bad(right):\n            return is_bad(right) == is_ascending\n\n        # for data of different types, numbers are at the top\n        if type(left) is not type(right):\n            return isinstance(left, float) == is_ascending\n\n        # case insensitive comparison for strings\n        if isinstance(left, str):\n            return left.upper() < right.upper()\n\n        # otherwise, compare numbers\n        return left < right\n\n\nclass ScoreTable(OWComponent, QObject):\n    shown_scores = \\\n        Setting(set(chain(*BUILTIN_SCORERS_ORDER.values())))\n\n    shownScoresChanged = Signal()\n\n    class ItemDelegate(QStyledItemDelegate):\n        def sizeHint(self, *args):\n            size = super().sizeHint(*args)\n            return QSize(size.width(), size.height() + 6)\n\n        def displayText(self, value, locale):\n            if isinstance(value, float):\n                return f\"{value:.3f}\"\n            else:\n                return super().displayText(value, locale)\n\n    def __init__(self, master):\n        QObject.__init__(self)\n        OWComponent.__init__(self, master)\n\n        self.view = gui.TableView(\n            wordWrap=True, editTriggers=gui.TableView.NoEditTriggers\n        )\n        header = self.view.horizontalHeader()\n        header.setSectionResizeMode(QHeaderView.ResizeToContents)\n        header.setDefaultAlignment(Qt.AlignCenter)\n        header.setStretchLastSection(False)\n        header.setContextMenuPolicy(Qt.CustomContextMenu)\n        header.customContextMenuRequested.connect(self.show_column_chooser)\n\n        self.model = QStandardItemModel(master)\n        self.model.setHorizontalHeaderLabels([\"Method\"])\n        self.sorted_model = ScoreModel()\n        self.sorted_model.setSourceModel(self.model)\n        self.view.setModel(self.sorted_model)\n        self.view.setItemDelegate(self.ItemDelegate())\n\n    def _column_names(self):\n        return (self.model.horizontalHeaderItem(section).data(Qt.DisplayRole)\n                for section in range(1, self.model.columnCount()))\n\n    def show_column_chooser(self, pos):\n        # pylint doesn't know that self.shown_scores is a set, not a Setting\n        # pylint: disable=unsupported-membership-test\n        def update(col_name, checked):\n            if checked:\n                self.shown_scores.add(col_name)\n            else:\n                self.shown_scores.remove(col_name)\n            self._update_shown_columns()\n\n        menu = QMenu()\n        header = self.view.horizontalHeader()\n        for col_name in self._column_names():\n            action = menu.addAction(col_name)\n            action.setCheckable(True)\n            action.setChecked(col_name in self.shown_scores)\n            action.triggered.connect(partial(update, col_name))\n        menu.exec(header.mapToGlobal(pos))\n\n    def _update_shown_columns(self):\n        # pylint doesn't know that self.shown_scores is a set, not a Setting\n        # pylint: disable=unsupported-membership-test\n        self.view.resizeColumnsToContents()\n        header = self.view.horizontalHeader()\n        for section, col_name in enumerate(self._column_names(), start=1):\n            header.setSectionHidden(section, col_name not in self.shown_scores)\n        self.shownScoresChanged.emit()\n\n    def update_header(self, scorers):\n        # Set the correct horizontal header labels on the results_model.\n        self.model.setColumnCount(3 + len(scorers))\n        self.model.setHorizontalHeaderItem(0, QStandardItem(\"Model\"))\n        self.model.setHorizontalHeaderItem(1, QStandardItem(\"Train time [s]\"))\n        self.model.setHorizontalHeaderItem(2, QStandardItem(\"Test time [s]\"))\n        for col, score in enumerate(scorers, start=3):\n            item = QStandardItem(score.name)\n            item.setToolTip(score.long_name)\n            self.model.setHorizontalHeaderItem(col, item)\n        self._update_shown_columns()\n\n    def copy_selection_to_clipboard(self):\n        mime = table_selection_to_mime_data(self.view)\n        QApplication.clipboard().setMimeData(\n            mime, QClipboard.Clipboard\n        )\n", "path": "Orange/widgets/evaluate/utils.py"}]}