{
    "file_diffs": [
        {
            "old_file_content": "import re\nimport html\nfrom enum import IntEnum\nfrom typing import Optional, Tuple, Union, Callable, NamedTuple, Dict, List\n\nfrom AnyQt.QtCore import (\n    Qt, QTimer, QPoint, QItemSelectionModel, QSize, QAbstractListModel)\nfrom AnyQt.QtGui import (\n    QValidator, QPalette, QDoubleValidator, QIntValidator,  QColor)\nfrom AnyQt.QtWidgets import (\n    QListView, QHBoxLayout, QStyledItemDelegate, QButtonGroup, QWidget,\n    QLineEdit, QToolTip, QLabel, QApplication,\n    QSpinBox, QSizePolicy, QRadioButton, QComboBox)\n\nfrom orangewidget.settings import Setting\nfrom orangewidget.utils import listview\n\nfrom Orange.data import (\n    Variable, ContinuousVariable, DiscreteVariable, TimeVariable, Domain, Table)\nimport Orange.preprocess.discretize as disc\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.utils import unique_everseen\nfrom Orange.widgets.utils.itemmodels import DomainModel\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\nfrom Orange.widgets.data.oweditdomain import FixedSizeButton\n\n\nre_custom_sep = re.compile(r\"\\s*,\\s*\")\ntime_units = [\"year\", \"month\", \"day\", \"week\", \"hour\", \"minute\", \"second\"]\nINVALID_WIDTH = \"invalid width\"\nTOO_MANY_INTERVALS = \"too many intervals\"\n\n\ndef _fixed_width_discretization(\n        data: Table,\n        var: Union[ContinuousVariable, str, int],\n        width: str) -> Union[DiscreteVariable, str]:\n    \"\"\"\n    Discretize numeric variable with fixed bin width. Used in method definition.\n\n    Width is given as string (coming from line edit). The labels for the new\n    variable will have the same number of digits; this is more appropriate\n    than the number of digits in the original variable, which may be too large.\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        width: interval width\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    digits = len(width) - width.index(\".\") - 1 if \".\" in width else 0\n    try:\n        width = float(width)\n    except ValueError:\n        return INVALID_WIDTH\n    if width <= 0:\n        return INVALID_WIDTH\n    try:\n        return disc.FixedWidth(width, digits)(data, var)\n    except disc.TooManyIntervals:\n        return TOO_MANY_INTERVALS\n\n\n# pylint: disable=invalid-name\ndef _fixed_time_width_discretization(\n        data: Table,\n        var: Union[TimeVariable, str, int],\n        width: str, unit: int) -> Union[DiscreteVariable]:\n    \"\"\"\n    Discretize time variable with fixed bin width. Used in method definition.\n\n    Width is given as string (coming from line edit).\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        width: interval width\n        unit: 0 = year, 1 = month, 2 = week, 3 = day, 4 = hour, 5 = min, 6 = sec\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    try:\n        width = int(width)\n    except ValueError:\n        return INVALID_WIDTH\n    if width <= 0:\n        return INVALID_WIDTH\n    if unit == 3:  # week\n        width *= 7\n    unit -= unit >= 3\n    try:\n        return disc.FixedTimeWidth(width, unit)(data, var)\n    except disc.TooManyIntervals:\n        return TOO_MANY_INTERVALS\n\n\ndef _mdl_discretization(\n        data: Table,\n        var: Union[ContinuousVariable, str, int]) -> Union[DiscreteVariable, str]:\n    if not data.domain.has_discrete_class:\n        return \"no discrete class\"\n    return disc.EntropyMDL()(data, var)\n\n\ndef _custom_discretization(\n        _,\n        var: Union[ContinuousVariable, str, int],\n        points: str) -> Union[DiscreteVariable, str]:\n    \"\"\"\n    Discretize variable using custom thresholds. Used in method definition.\n\n    Thresholds are given as string (coming from line edit).\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        points: thresholds\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    try:\n        cuts = [float(x) for x in re_custom_sep.split(points.strip())]\n    except ValueError:\n        cuts = []\n    if any(x >= y for x, y in zip(cuts, cuts[1:])):\n        cuts = []\n    if not cuts:\n        return \"invalid cuts\"\n    return disc.Discretizer.create_discretized_var(var, cuts)\n\n\nclass Methods(IntEnum):\n    # pylint: disable=invalid-name\n    Default, Keep, MDL, EqualFreq, EqualWidth, Remove, Custom, Binning, \\\n        FixedWidth, FixedWidthTime = range(10)\n\n\nclass MethodDesc(NamedTuple):\n    \"\"\"\n    Definitions of all methods; used for creation of interface and calling\n    \"\"\"\n    id_: Methods  # Method id\n    label: str  # Label used for radio button\n    short_desc: str  # Short descriptions for list views\n    tooltip: str  # Tooltip for radio button\n    # Discretization function, see, e.g. fixed_width_discretization\n    function: Optional[Callable[..., Union[DiscreteVariable, str]]]\n    controls: Tuple[str, ...] = ()  # Widget attributes with related ux controls\n\n\nOptions: Dict[Methods, MethodDesc] = {\n    method.id_: method\n    for method in (\n        MethodDesc(Methods.Default,\n                   \"Use default setting\", \"default\",\n                   \"Treat the variable as defined in 'default setting'\",\n                   None,\n                   ()),\n        MethodDesc(Methods.Keep,\n                   \"Keep numeric\", \"keep\",\n                   \"Keep the variable as is\",\n                   lambda data, var: var,\n                   ()),\n        MethodDesc(Methods.MDL,\n                   \"Entropy vs. MDL\", \"entropy\",\n                   \"Split values until MDL exceeds the entropy (Fayyad-Irani)\\n\"\n                   \"(requires discrete class variable)\",\n                   _mdl_discretization,\n                   ()),\n        MethodDesc(Methods.EqualFreq,\n                   \"Equal frequency, intervals: \", \"equal freq, k={}\",\n                   \"Create bins with same number of instances\",\n                   lambda data, var, k: disc.EqualFreq(k)(data, var),\n                   (\"freq_spin\", )),\n        MethodDesc(Methods.EqualWidth,\n                   \"Equal width, intervals: \", \"equal width, k={}\",\n                   \"Create bins of the same width\",\n                   lambda data, var, k: disc.EqualWidth(k)(data, var),\n                   (\"width_spin\", )),\n        MethodDesc(Methods.Remove,\n                   \"Remove\", \"remove\",\n                   \"Remove variable\",\n                   lambda *_: None,\n                   ()),\n        MethodDesc(Methods.Binning,\n                   \"Natural binning, desired bins: \", \"binning, desired={}\",\n                   \"Create bins with nice thresholds; \"\n                   \"try matching desired number of bins\",\n                   lambda data, var, nbins: disc.Binning(nbins)(data, var),\n                   (\"binning_spin\", )),\n        MethodDesc(Methods.FixedWidth,\n                   \"Fixed width: \", \"fixed width {}\",\n                   \"Create bins with the given width (not for time variables)\",\n                   _fixed_width_discretization,\n                   (\"width_line\", )),\n        MethodDesc(Methods.FixedWidthTime,\n                   \"Time interval: \", \"time interval, {} {}\",\n                   \"Create bins with the give width (for time variables)\",\n                   _fixed_time_width_discretization,\n                   (\"width_time_line\", \"width_time_unit\")),\n        MethodDesc(Methods.Custom,\n                   \"Custom: \", \"custom: {}\",\n                   \"Use manually specified thresholds\",\n                   _custom_discretization,\n                   (\"threshold_line\", ))\n    )\n}\n\n\nclass VarHint(NamedTuple):\n    \"\"\"Description for settings\"\"\"\n    method_id: Methods\n    args: Tuple[Union[str, float, int]]\n\n\nclass DiscDesc(NamedTuple):\n    \"\"\"Data for list view model\"\"\"\n    hint: VarHint\n    points: str\n    values: Tuple[str]\n\n\nKeyType = Optional[Tuple[str, bool]]\nDefaultHint = VarHint(Methods.Keep, ())\nDefaultKey = None\n\n\ndef variable_key(var: ContinuousVariable) -> KeyType:\n    \"\"\"Key for that variable in var_hints and discretized_vars\"\"\"\n    return var.name, isinstance(var, TimeVariable)\n\n\nclass ListViewSearch(listview.ListViewSearch):\n    \"\"\"\n    A list view with two components shown above it:\n    - a listview containing a single item representing default settings\n    - a filter for search\n\n    The class is based on listview.ListViewSearch and needs to have the same\n    name in order to override its private method __layout.\n\n    Inherited __init__ calls __layout, so `default_view` must be constructed\n    there. Construction before calling super().__init__ doesn't work because\n    PyQt does not allow it.\n    \"\"\"\n    class DiscDelegate(QStyledItemDelegate):\n        \"\"\"\n        A delegate that shows items (variables) with specific settings in bold\n        \"\"\"\n        def initStyleOption(self, option, index):\n            super().initStyleOption(option, index)\n            option.font.setBold(index.data(Qt.UserRole).hint is not None)\n\n    def __init__(self, *args, **kwargs):\n        self.default_view = None\n        super().__init__(preferred_size=QSize(350, -1), *args, **kwargs)\n        self.setItemDelegate(self.DiscDelegate(self))\n\n    def select_default(self):\n        \"\"\"Select the item representing default settings\"\"\"\n        index = self.default_view.model().index(0)\n        self.default_view.selectionModel().select(\n            index, QItemSelectionModel.Select)\n\n    # pylint: disable=unused-private-member\n    def __layout(self):\n        if self.default_view is None:  # __layout was called from __init__\n            view = self.default_view = QListView(self)\n            view.setModel(DefaultDiscModel())\n            view.verticalScrollBar().setDisabled(True)\n            view.horizontalScrollBar().setDisabled(True)\n            view.setHorizontalScrollBarPolicy(\n                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)\n            view.setVerticalScrollBarPolicy(\n                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)\n            font = view.font()\n            font.setBold(True)\n            view.setFont(font)\n        else:\n            view = self.default_view\n\n        # Put the list view with default on top\n        margins = self.viewportMargins()\n        def_height = view.sizeHintForRow(0) + 2 * view.spacing() + 2\n        view.setGeometry(0, 0, self.geometry().width(), def_height)\n        view.setFixedHeight(def_height)\n\n        # Then search\n        search = self.__search\n        src_height = search.sizeHint().height()\n        size = self.size()\n        search.setGeometry(0, def_height + 2, size.width(), src_height)\n\n        # Then the real list view\n        margins.setTop(def_height + 2 + src_height)\n        self.setViewportMargins(margins)\n\n\ndef format_desc(hint: VarHint) -> str:\n    \"\"\"Describe the method and its parameters; used in list views and report\"\"\"\n    if hint is None:\n        return Options[Methods.Default].short_desc\n    desc = Options[hint.method_id].short_desc\n    if hint.method_id == Methods.FixedWidthTime:\n        width, unit = hint.args\n        unit = time_units[unit]\n        try:\n            width = int(width)\n        except ValueError:\n            unit += \"(s)\"\n        else:\n            if width != 1:\n                unit += \"s\"\n        return desc.format(width, unit)\n    return desc.format(*hint.args)\n\n\nclass DiscDomainModel(DomainModel):\n    \"\"\"\n    Domain model that adds description of discretization methods and thresholds\n\n    Also provides a tooltip that shows bins, that is, labels of the discretized\n    variable.\n    \"\"\"\n    def data(self, index, role=Qt.DisplayRole):\n        if role == Qt.ToolTipRole:\n            var = self[index.row()]\n            data = index.data(Qt.UserRole)\n            tip = f\"<b>{var.name}: </b>\"\n            values = map(html.escape, data.values)\n            if not data.values:\n                return None\n            if len(data.values) <= 3:\n                return f'<p style=\"white-space:pre\">{tip}' \\\n                       f'{\",&nbsp;&nbsp;\".join(values)}</p>'\n            else:\n                return tip + \"<br/>\" \\\n                    + \"\".join(f\"- {value}<br/>\" for value in values)\n        value = super().data(index, role)\n        if role == Qt.DisplayRole:\n            hint, points, values = index.data(Qt.UserRole)\n            value += f\" ({format_desc(hint)}){points}\"\n        return value\n\n\nclass DefaultDiscModel(QAbstractListModel):\n    \"\"\"\n    A model used for showing \"Default settings\" above the list view with var\n    \"\"\"\n    icon = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if DefaultDiscModel.icon is None:\n            DefaultDiscModel.icon = gui.createAttributePixmap(\n                \"★\", QColor(0, 0, 0, 0), Qt.black)\n        self.hint: VarHint = DefaultHint\n\n    @staticmethod\n    def rowCount(parent):\n        return 0 if parent.isValid() else 1\n\n    @staticmethod\n    def columnCount(parent):\n        return 0 if parent.isValid() else 1\n\n    def data(self, _, role=Qt.DisplayRole):\n        if role == Qt.DisplayRole:\n            return \"Default setting: \" + format_desc(self.hint)\n        elif role == Qt.DecorationRole:\n            return DefaultDiscModel.icon\n        elif role == Qt.ToolTipRole:\n            return \"Default setting for variables without specific setings\"\n        return None\n\n    def setData(self, index, value, role=Qt.DisplayRole):\n        if role == Qt.UserRole:\n            self.hint = value\n            self.dataChanged.emit(index, index)\n\n\nclass IncreasingNumbersListValidator(QValidator):\n    \"\"\"\n    A validator for custom thresholds\n\n    Requires a string with increasing comma-separated values. If the string\n    ends with number followed by space, it inserts a comma.\n    \"\"\"\n    @staticmethod\n    def validate(string: str, pos: int) -> Tuple[QValidator.State, str, int]:\n        for i, c in enumerate(string, start=1):\n            if c not in \"+-., 0123456789\":\n                return QValidator.Invalid, string, i\n        prev = None\n        if pos == len(string) >= 2 \\\n                and string[-1] == \" \" and string[-2].isdigit():\n            string = string[:-1] + \", \"\n            pos += 1\n        for valuestr in re_custom_sep.split(string.strip()):\n            try:\n                value = float(valuestr)\n            except ValueError:\n                return QValidator.Intermediate, string, pos\n            if prev is not None and value <= prev:\n                return QValidator.Intermediate, string, pos\n            prev = value\n        return QValidator.Acceptable, string, pos\n\n    @staticmethod\n    def show_tip(\n            widget: QWidget, pos: QPoint, text: str, timeout=-1,\n            textFormat=Qt.AutoText, wordWrap=None):\n        \"\"\"Show a tooltip; used for invalid custom thresholds\"\"\"\n        propname = __name__ + \"::show_tip_qlabel\"\n        if timeout < 0:\n            timeout = widget.toolTipDuration()\n        if timeout < 0:\n            timeout = 5000 + 40 * max(0, len(text) - 100)\n        tip = widget.property(propname)\n        if not text and tip is None:\n            return\n\n        def hide():\n            w = tip.parent()\n            w.setProperty(propname, None)\n            tip.timer.stop()\n            tip.close()\n            tip.deleteLater()\n\n        if not isinstance(tip, QLabel):\n            tip = QLabel(objectName=\"tip-label\", focusPolicy=Qt.NoFocus)\n            tip.setBackgroundRole(QPalette.ToolTipBase)\n            tip.setForegroundRole(QPalette.ToolTipText)\n            tip.setPalette(QToolTip.palette())\n            tip.setFont(QApplication.font(\"QTipLabel\"))\n            tip.setContentsMargins(2, 2, 2, 2)\n            tip.timer = QTimer(tip, singleShot=True, objectName=\"hide-timer\")\n            tip.timer.timeout.connect(hide)\n            widget.setProperty(propname, tip)\n            tip.setParent(widget, Qt.ToolTip)\n\n        tip.setText(text)\n        tip.setTextFormat(textFormat)\n        if wordWrap is None:\n            wordWrap = textFormat != Qt.PlainText\n        tip.setWordWrap(wordWrap)\n\n        if not text:\n            hide()\n        else:\n            tip.timer.start(timeout)\n            tip.show()\n            tip.move(pos)\n\n\n# These are no longer used, but needed for loading and migrating old pickles.\n# We insert them into namespace instead of normally defining them, in order\n# to hide it from IDE's and avoid mistakenly using them.\n# pylint: disable=wrong-import-position,wrong-import-order\nfrom collections import namedtuple\nglobals().update(dict(\n    DState=namedtuple(\n        \"DState\",\n        [\"method\",  # discretization method\n         \"points\",  # induced cut points\n         \"disc_var\"],\n        defaults=(None, None)  # induced discretized variable\n    ),\n    Default=namedtuple(\"Default\", [\"method\"]),\n    Leave=namedtuple(\"Leave\", []),\n    MDL=namedtuple(\"MDL\", []),\n    EqualFreq=namedtuple(\"EqualFreq\", [\"k\"]),\n    EqualWidth=namedtuple(\"EqualWidth\", [\"k\"]),\n    Remove=namedtuple(\"Remove\", []),\n    Custom=namedtuple(\"Custom\", [\"points\"])\n))\n\n\nclass OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    #WidgetPreview(OWDiscretize).run(Table(\"/Users/janez/Downloads/banking-crises.tab\"))\n    WidgetPreview(OWDiscretize).run(Table(\"heart_disease\"))\n",
            "new_file_content": "import re\nimport html\nfrom enum import IntEnum\nfrom typing import Optional, Tuple, Union, Callable, NamedTuple, Dict, List\n\nfrom AnyQt.QtCore import (\n    Qt, QTimer, QPoint, QItemSelectionModel, QSize, QAbstractListModel)\nfrom AnyQt.QtGui import (\n    QValidator, QPalette, QDoubleValidator, QIntValidator,  QColor)\nfrom AnyQt.QtWidgets import (\n    QListView, QHBoxLayout, QStyledItemDelegate, QButtonGroup, QWidget,\n    QLineEdit, QToolTip, QLabel, QApplication,\n    QSpinBox, QSizePolicy, QRadioButton, QComboBox)\n\nfrom orangewidget.settings import Setting\nfrom orangewidget.utils import listview\n\nfrom Orange.data import (\n    Variable, ContinuousVariable, DiscreteVariable, TimeVariable, Domain, Table)\nimport Orange.preprocess.discretize as disc\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.utils import unique_everseen\nfrom Orange.widgets.utils.itemmodels import DomainModel\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\nfrom Orange.widgets.data.oweditdomain import FixedSizeButton\n\n\nre_custom_sep = re.compile(r\"\\s*,\\s*\")\ntime_units = [\"year\", \"month\", \"day\", \"week\", \"hour\", \"minute\", \"second\"]\nINVALID_WIDTH = \"invalid width\"\nTOO_MANY_INTERVALS = \"too many intervals\"\n\n\ndef _fixed_width_discretization(\n        data: Table,\n        var: Union[ContinuousVariable, str, int],\n        width: str) -> Union[DiscreteVariable, str]:\n    \"\"\"\n    Discretize numeric variable with fixed bin width. Used in method definition.\n\n    Width is given as string (coming from line edit). The labels for the new\n    variable will have the same number of digits; this is more appropriate\n    than the number of digits in the original variable, which may be too large.\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        width: interval width\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    digits = len(width) - width.index(\".\") - 1 if \".\" in width else 0\n    try:\n        width = float(width)\n    except ValueError:\n        return INVALID_WIDTH\n    if width <= 0:\n        return INVALID_WIDTH\n    try:\n        return disc.FixedWidth(width, digits)(data, var)\n    except disc.TooManyIntervals:\n        return TOO_MANY_INTERVALS\n\n\n# pylint: disable=invalid-name\ndef _fixed_time_width_discretization(\n        data: Table,\n        var: Union[TimeVariable, str, int],\n        width: str, unit: int) -> Union[DiscreteVariable]:\n    \"\"\"\n    Discretize time variable with fixed bin width. Used in method definition.\n\n    Width is given as string (coming from line edit).\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        width: interval width\n        unit: 0 = year, 1 = month, 2 = week, 3 = day, 4 = hour, 5 = min, 6 = sec\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    try:\n        width = int(width)\n    except ValueError:\n        return INVALID_WIDTH\n    if width <= 0:\n        return INVALID_WIDTH\n    if unit == 3:  # week\n        width *= 7\n    unit -= unit >= 3\n    try:\n        return disc.FixedTimeWidth(width, unit)(data, var)\n    except disc.TooManyIntervals:\n        return TOO_MANY_INTERVALS\n\n\ndef _mdl_discretization(\n        data: Table,\n        var: Union[ContinuousVariable, str, int]) -> Union[DiscreteVariable, str]:\n    if not data.domain.has_discrete_class:\n        return \"no discrete class\"\n    return disc.EntropyMDL()(data, var)\n\n\ndef _custom_discretization(\n        _,\n        var: Union[ContinuousVariable, str, int],\n        points: str) -> Union[DiscreteVariable, str]:\n    \"\"\"\n    Discretize variable using custom thresholds. Used in method definition.\n\n    Thresholds are given as string (coming from line edit).\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        points: thresholds\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    try:\n        cuts = [float(x) for x in re_custom_sep.split(points.strip())]\n    except ValueError:\n        cuts = []\n    if any(x >= y for x, y in zip(cuts, cuts[1:])):\n        cuts = []\n    if not cuts:\n        return \"invalid cuts\"\n    return disc.Discretizer.create_discretized_var(var, cuts)\n\n\nclass Methods(IntEnum):\n    # pylint: disable=invalid-name\n    Default, Keep, MDL, EqualFreq, EqualWidth, Remove, Custom, Binning, \\\n        FixedWidth, FixedWidthTime = range(10)\n\n\nclass MethodDesc(NamedTuple):\n    \"\"\"\n    Definitions of all methods; used for creation of interface and calling\n    \"\"\"\n    id_: Methods  # Method id\n    label: str  # Label used for radio button\n    short_desc: str  # Short descriptions for list views\n    tooltip: str  # Tooltip for radio button\n    # Discretization function, see, e.g. fixed_width_discretization\n    function: Optional[Callable[..., Union[DiscreteVariable, str]]]\n    controls: Tuple[str, ...] = ()  # Widget attributes with related ux controls\n\n\nOptions: Dict[Methods, MethodDesc] = {\n    method.id_: method\n    for method in (\n        MethodDesc(Methods.Default,\n                   \"Use default setting\", \"default\",\n                   \"Treat the variable as defined in 'default setting'\",\n                   None,\n                   ()),\n        MethodDesc(Methods.Keep,\n                   \"Keep numeric\", \"keep\",\n                   \"Keep the variable as is\",\n                   lambda data, var: var,\n                   ()),\n        MethodDesc(Methods.MDL,\n                   \"Entropy vs. MDL\", \"entropy\",\n                   \"Split values until MDL exceeds the entropy (Fayyad-Irani)\\n\"\n                   \"(requires discrete class variable)\",\n                   _mdl_discretization,\n                   ()),\n        MethodDesc(Methods.EqualFreq,\n                   \"Equal frequency, intervals: \", \"equal freq, k={}\",\n                   \"Create bins with same number of instances\",\n                   lambda data, var, k: disc.EqualFreq(k)(data, var),\n                   (\"freq_spin\", )),\n        MethodDesc(Methods.EqualWidth,\n                   \"Equal width, intervals: \", \"equal width, k={}\",\n                   \"Create bins of the same width\",\n                   lambda data, var, k: disc.EqualWidth(k)(data, var),\n                   (\"width_spin\", )),\n        MethodDesc(Methods.Remove,\n                   \"Remove\", \"remove\",\n                   \"Remove variable\",\n                   lambda *_: None,\n                   ()),\n        MethodDesc(Methods.Binning,\n                   \"Natural binning, desired bins: \", \"binning, desired={}\",\n                   \"Create bins with nice thresholds; \"\n                   \"try matching desired number of bins\",\n                   lambda data, var, nbins: disc.Binning(nbins)(data, var),\n                   (\"binning_spin\", )),\n        MethodDesc(Methods.FixedWidth,\n                   \"Fixed width: \", \"fixed width {}\",\n                   \"Create bins with the given width (not for time variables)\",\n                   _fixed_width_discretization,\n                   (\"width_line\", )),\n        MethodDesc(Methods.FixedWidthTime,\n                   \"Time interval: \", \"time interval, {} {}\",\n                   \"Create bins with the give width (for time variables)\",\n                   _fixed_time_width_discretization,\n                   (\"width_time_line\", \"width_time_unit\")),\n        MethodDesc(Methods.Custom,\n                   \"Custom: \", \"custom: {}\",\n                   \"Use manually specified thresholds\",\n                   _custom_discretization,\n                   (\"threshold_line\", ))\n    )\n}\n\n\nclass VarHint(NamedTuple):\n    \"\"\"Description for settings\"\"\"\n    method_id: Methods\n    args: Tuple[Union[str, float, int]]\n\n\nclass DiscDesc(NamedTuple):\n    \"\"\"Data for list view model\"\"\"\n    hint: VarHint\n    points: str\n    values: Tuple[str]\n\n\nKeyType = Optional[Tuple[str, bool]]\nDefaultHint = VarHint(Methods.Keep, ())\nDefaultKey = None\n\n\ndef variable_key(var: ContinuousVariable) -> KeyType:\n    \"\"\"Key for that variable in var_hints and discretized_vars\"\"\"\n    return var.name, isinstance(var, TimeVariable)\n\n\nclass ListViewSearch(listview.ListViewSearch):\n    \"\"\"\n    A list view with two components shown above it:\n    - a listview containing a single item representing default settings\n    - a filter for search\n\n    The class is based on listview.ListViewSearch and needs to have the same\n    name in order to override its private method __layout.\n\n    Inherited __init__ calls __layout, so `default_view` must be constructed\n    there. Construction before calling super().__init__ doesn't work because\n    PyQt does not allow it.\n    \"\"\"\n    class DiscDelegate(QStyledItemDelegate):\n        \"\"\"\n        A delegate that shows items (variables) with specific settings in bold\n        \"\"\"\n        def initStyleOption(self, option, index):\n            super().initStyleOption(option, index)\n            option.font.setBold(index.data(Qt.UserRole).hint is not None)\n\n    def __init__(self, *args, **kwargs):\n        self.default_view = None\n        super().__init__(preferred_size=QSize(350, -1), *args, **kwargs)\n        self.setItemDelegate(self.DiscDelegate(self))\n\n    def select_default(self):\n        \"\"\"Select the item representing default settings\"\"\"\n        index = self.default_view.model().index(0)\n        self.default_view.selectionModel().select(\n            index, QItemSelectionModel.Select)\n\n    # pylint: disable=unused-private-member\n    def __layout(self):\n        if self.default_view is None:  # __layout was called from __init__\n            view = self.default_view = QListView(self)\n            view.setModel(DefaultDiscModel())\n            view.verticalScrollBar().setDisabled(True)\n            view.horizontalScrollBar().setDisabled(True)\n            view.setHorizontalScrollBarPolicy(\n                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)\n            view.setVerticalScrollBarPolicy(\n                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)\n            font = view.font()\n            font.setBold(True)\n            view.setFont(font)\n        else:\n            view = self.default_view\n\n        # Put the list view with default on top\n        margins = self.viewportMargins()\n        def_height = view.sizeHintForRow(0) + 2 * view.spacing() + 2\n        view.setGeometry(0, 0, self.geometry().width(), def_height)\n        view.setFixedHeight(def_height)\n\n        # Then search\n        search = self.__search\n        src_height = search.sizeHint().height()\n        size = self.size()\n        search.setGeometry(0, def_height + 2, size.width(), src_height)\n\n        # Then the real list view\n        margins.setTop(def_height + 2 + src_height)\n        self.setViewportMargins(margins)\n\n\ndef format_desc(hint: VarHint) -> str:\n    \"\"\"Describe the method and its parameters; used in list views and report\"\"\"\n    if hint is None:\n        return Options[Methods.Default].short_desc\n    desc = Options[hint.method_id].short_desc\n    if hint.method_id == Methods.FixedWidthTime:\n        width, unit = hint.args\n        unit = time_units[unit]\n        try:\n            width = int(width)\n        except ValueError:\n            unit += \"(s)\"\n        else:\n            if width != 1:\n                unit += \"s\"\n        return desc.format(width, unit)\n    return desc.format(*hint.args)\n\n\nclass DiscDomainModel(DomainModel):\n    \"\"\"\n    Domain model that adds description of discretization methods and thresholds\n\n    Also provides a tooltip that shows bins, that is, labels of the discretized\n    variable.\n    \"\"\"\n    def data(self, index, role=Qt.DisplayRole):\n        if role == Qt.ToolTipRole:\n            var = self[index.row()]\n            data = index.data(Qt.UserRole)\n            tip = f\"<b>{var.name}: </b>\"\n            values = map(html.escape, data.values)\n            if not data.values:\n                return None\n            if len(data.values) <= 3:\n                return f'<p style=\"white-space:pre\">{tip}' \\\n                       f'{\",&nbsp;&nbsp;\".join(values)}</p>'\n            else:\n                return tip + \"<br/>\" \\\n                    + \"\".join(f\"- {value}<br/>\" for value in values)\n        value = super().data(index, role)\n        if role == Qt.DisplayRole:\n            hint, points, values = index.data(Qt.UserRole)\n            value += f\" ({format_desc(hint)}){points}\"\n        return value\n\n\nclass DefaultDiscModel(QAbstractListModel):\n    \"\"\"\n    A model used for showing \"Default settings\" above the list view with var\n    \"\"\"\n    icon = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if DefaultDiscModel.icon is None:\n            DefaultDiscModel.icon = gui.createAttributePixmap(\n                \"★\", QColor(0, 0, 0, 0), Qt.black)\n        self.hint: VarHint = DefaultHint\n\n    @staticmethod\n    def rowCount(parent):\n        return 0 if parent.isValid() else 1\n\n    @staticmethod\n    def columnCount(parent):\n        return 0 if parent.isValid() else 1\n\n    def data(self, _, role=Qt.DisplayRole):\n        if role == Qt.DisplayRole:\n            return \"Default setting: \" + format_desc(self.hint)\n        elif role == Qt.DecorationRole:\n            return DefaultDiscModel.icon\n        elif role == Qt.ToolTipRole:\n            return \"Default setting for variables without specific setings\"\n        return None\n\n    def setData(self, index, value, role=Qt.DisplayRole):\n        if role == Qt.UserRole:\n            self.hint = value\n            self.dataChanged.emit(index, index)\n\n\nclass IncreasingNumbersListValidator(QValidator):\n    \"\"\"\n    A validator for custom thresholds\n\n    Requires a string with increasing comma-separated values. If the string\n    ends with number followed by space, it inserts a comma.\n    \"\"\"\n    @staticmethod\n    def validate(string: str, pos: int) -> Tuple[QValidator.State, str, int]:\n        for i, c in enumerate(string, start=1):\n            if c not in \"+-., 0123456789\":\n                return QValidator.Invalid, string, i\n        prev = None\n        if pos == len(string) >= 2 \\\n                and string[-1] == \" \" and string[-2].isdigit():\n            string = string[:-1] + \", \"\n            pos += 1\n        for valuestr in re_custom_sep.split(string.strip()):\n            try:\n                value = float(valuestr)\n            except ValueError:\n                return QValidator.Intermediate, string, pos\n            if prev is not None and value <= prev:\n                return QValidator.Intermediate, string, pos\n            prev = value\n        return QValidator.Acceptable, string, pos\n\n    @staticmethod\n    def show_tip(\n            widget: QWidget, pos: QPoint, text: str, timeout=-1,\n            textFormat=Qt.AutoText, wordWrap=None):\n        \"\"\"Show a tooltip; used for invalid custom thresholds\"\"\"\n        propname = __name__ + \"::show_tip_qlabel\"\n        if timeout < 0:\n            timeout = widget.toolTipDuration()\n        if timeout < 0:\n            timeout = 5000 + 40 * max(0, len(text) - 100)\n        tip = widget.property(propname)\n        if not text and tip is None:\n            return\n\n        def hide():\n            w = tip.parent()\n            w.setProperty(propname, None)\n            tip.timer.stop()\n            tip.close()\n            tip.deleteLater()\n\n        if not isinstance(tip, QLabel):\n            tip = QLabel(objectName=\"tip-label\", focusPolicy=Qt.NoFocus)\n            tip.setBackgroundRole(QPalette.ToolTipBase)\n            tip.setForegroundRole(QPalette.ToolTipText)\n            tip.setPalette(QToolTip.palette())\n            tip.setFont(QApplication.font(\"QTipLabel\"))\n            tip.setContentsMargins(2, 2, 2, 2)\n            tip.timer = QTimer(tip, singleShot=True, objectName=\"hide-timer\")\n            tip.timer.timeout.connect(hide)\n            widget.setProperty(propname, tip)\n            tip.setParent(widget, Qt.ToolTip)\n\n        tip.setText(text)\n        tip.setTextFormat(textFormat)\n        if wordWrap is None:\n            wordWrap = textFormat != Qt.PlainText\n        tip.setWordWrap(wordWrap)\n\n        if not text:\n            hide()\n        else:\n            tip.timer.start(timeout)\n            tip.show()\n            tip.move(pos)\n\n\n# These are no longer used, but needed for loading and migrating old pickles.\n# We insert them into namespace instead of normally defining them, in order\n# to hide it from IDE's and avoid mistakenly using them.\n# pylint: disable=wrong-import-position,wrong-import-order\nfrom collections import namedtuple\nglobals().update(dict(\n    DState=namedtuple(\n        \"DState\",\n        [\"method\",  # discretization method\n         \"points\",  # induced cut points\n         \"disc_var\"],\n        defaults=(None, None)  # induced discretized variable\n    ),\n    Default=namedtuple(\"Default\", [\"method\"]),\n    Leave=namedtuple(\"Leave\", []),\n    MDL=namedtuple(\"MDL\", []),\n    EqualFreq=namedtuple(\"EqualFreq\", [\"k\"]),\n    EqualWidth=namedtuple(\"EqualWidth\", [\"k\"]),\n    Remove=namedtuple(\"Remove\", []),\n    Custom=namedtuple(\"Custom\", [\"points\"])\n))\n\n\nclass OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    #WidgetPreview(OWDiscretize).run(Table(\"/Users/janez/Downloads/banking-crises.tab\"))\n    WidgetPreview(OWDiscretize).run(Table(\"heart_disease\"))\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/owdiscretize.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "625582096",
                "new_commit_hash": "298bb5828",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/owdiscretize.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/owdiscretize.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 882,
                            "length": 6
                        },
                        "new_range": {
                            "start": 882,
                            "length": 7
                        },
                        "section": "class OWDiscretize(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        set_enabled(Methods.FixedWidth, True)",
                                "type": "context"
                            },
                            {
                                "content": "        set_enabled(Methods.FixedWidthTime, True)",
                                "type": "context"
                            },
                            {
                                "content": "        set_enabled(Methods.Custom, True)",
                                "type": "context"
                            },
                            {
                                "content": "        self.copy_to_custom.setEnabled(False)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def _var_selection_changed(self, _):",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"Callback for changed selection in listview with variables\"\"\"",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owdiscretize.py",
                            "type": "class",
                            "name": "OWDiscretize",
                            "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 484,
                            "end_lineno": 1016,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/owdiscretize.py",
                            "type": "method",
                            "name": "OWDiscretize._default_selected",
                            "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 872,
                            "end_lineno": 885,
                            "parent": {
                                "file_name": "Orange/widgets/data/owdiscretize.py",
                                "type": "class",
                                "name": "OWDiscretize",
                                "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 484,
                                "end_lineno": 1016,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 900,
                            "length": 6
                        },
                        "new_range": {
                            "start": 901,
                            "length": 7
                        },
                        "section": "class OWDiscretize(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        set_enabled(Methods.Default, True)",
                                "type": "context"
                            },
                            {
                                "content": "        set_enabled(Methods.FixedWidth, no_time)",
                                "type": "context"
                            },
                            {
                                "content": "        set_enabled(Methods.Custom, no_time)",
                                "type": "context"
                            },
                            {
                                "content": "        self.copy_to_custom.setEnabled(no_time)",
                                "type": "added"
                            },
                            {
                                "content": "        set_enabled(Methods.FixedWidthTime, all_time)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def _update_interface(self):",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owdiscretize.py",
                            "type": "method",
                            "name": "OWDiscretize._var_selection_changed",
                            "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 887,
                            "end_lineno": 905,
                            "parent": {
                                "file_name": "Orange/widgets/data/owdiscretize.py",
                                "type": "class",
                                "name": "OWDiscretize",
                                "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 484,
                                "end_lineno": 1016,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/owdiscretize.py",
                            "type": "class",
                            "name": "OWDiscretize",
                            "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 484,
                            "end_lineno": 1016,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 937,
                            "length": 6
                        },
                        "new_range": {
                            "start": 939,
                            "length": 7
                        },
                        "section": "class OWDiscretize(widget.OWWidget):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.varview.model().set_domain(None if data is None else data.domain)",
                                "type": "context"
                            },
                            {
                                "content": "        self._update_discretizations()",
                                "type": "context"
                            },
                            {
                                "content": "        self._update_default_model()",
                                "type": "context"
                            },
                            {
                                "content": "        self.varview.select_default()",
                                "type": "added"
                            },
                            {
                                "content": "        self._set_mdl_button()",
                                "type": "context"
                            },
                            {
                                "content": "        self.commit.now()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/owdiscretize.py",
                            "type": "class",
                            "name": "OWDiscretize",
                            "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 484,
                            "end_lineno": 1016,
                            "parent": null
                        },
                        {
                            "file_name": "Orange/widgets/data/owdiscretize.py",
                            "type": "method",
                            "name": "OWDiscretize.set_data",
                            "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 936,
                            "end_lineno": 944,
                            "parent": {
                                "file_name": "Orange/widgets/data/owdiscretize.py",
                                "type": "class",
                                "name": "OWDiscretize",
                                "content": "class OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n        self.copy_to_custom.setEnabled(False)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        self.copy_to_custom.setEnabled(no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self.varview.select_default()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 484,
                                "end_lineno": 1016,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring,unsubscriptable-object,protected-access\nimport unittest\nfrom functools import partial\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QPoint, Qt, QModelIndex\nfrom AnyQt.QtWidgets import QWidget, QApplication, QStyleOptionViewItem\nfrom AnyQt.QtGui import QIcon\n\nfrom orangewidget.settings import Context\n\nfrom Orange.data import Table, ContinuousVariable, TimeVariable, Domain\nfrom Orange.preprocess.discretize import TooManyIntervals\nfrom Orange.widgets.data.owdiscretize import OWDiscretize, \\\n    IncreasingNumbersListValidator, VarHint, Methods, DefaultKey, \\\n    _fixed_width_discretization, _fixed_time_width_discretization, \\\n    _custom_discretization, variable_key, Options, DefaultHint, \\\n    _mdl_discretization, ListViewSearch, format_desc, DefaultDiscModel\nfrom Orange.widgets.tests.base import WidgetTest, GuiTest\nfrom Orange.widgets.utils.itemmodels import select_rows\n\n\nclass DataMixin:\n    def prepare_data(self):\n        self.domain = Domain([ContinuousVariable(\"x\"),\n                              ContinuousVariable(\"y\"),\n                              ContinuousVariable(\"z\"),\n                              TimeVariable(\"t\"),\n                              TimeVariable(\"u\")])\n        self.data = Table.from_numpy(self.domain, np.arange(20).reshape(4, 5))\n        self.var_hints = {\n            DefaultKey: VarHint(Methods.Keep, ()),\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.Keep, ()),\n            (\"z\", False): VarHint(Methods.Remove, ()),\n            (\"t\", True): VarHint(Methods.Binning, (2, ))\n        }\n        # Copy the following line to tests, for reference:\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n\nclass TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})\n\n\nclass TestValidator(unittest.TestCase):\n    def test_validate(self):\n        v = IncreasingNumbersListValidator()\n        self.assertEqual(v.validate(\"\", 0), (v.Intermediate, '', 0))\n        self.assertEqual(v.validate(\"1\", 1), (v.Acceptable, '1', 1))\n        self.assertEqual(v.validate(\",\", 0), (v.Intermediate, ',', 0))\n        self.assertEqual(v.validate(\"-\", 0), (v.Intermediate, '-', 0))\n        self.assertEqual(v.validate(\"1,,\", 1), (v.Intermediate, '1,,', 1))\n        self.assertEqual(v.validate(\"1,a,\", 1), (v.Invalid, '1,a,', 3))\n        self.assertEqual(v.validate(\"a\", 1), (v.Invalid, 'a', 1))\n        self.assertEqual(v.validate(\"1,1\", 0), (v.Intermediate, '1,1', 0))\n        self.assertEqual(v.validate(\"1,12\", 0), (v.Acceptable, '1,12', 0))\n\n        self.assertEqual(v.validate(\"1, 2 \", 5), (v.Intermediate, \"1, 2, \", 6))\n\n\nclass TestModels(WidgetTest, DataMixin):\n    def setUp(self):\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n    def test_delegate(self):\n        self.prepare_data()\n        w = self.widget\n        w.var_hints = self.var_hints\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        self.send_signal(w.Inputs.data, self.data)\n\n        model = w.varview.model()\n        delegate: ListViewSearch.DiscDelegate = w.varview.itemDelegate()\n        option = QStyleOptionViewItem()\n        delegate.initStyleOption(option, model.index(0))\n        self.assertTrue(option.font.bold())\n\n        option = QStyleOptionViewItem()\n        delegate.initStyleOption(option, model.index(4))\n        self.assertFalse(option.font.bold())\n\n    def test_layout(self):\n        # Not much to test, just don't crash\n        self.widget.varview.updateGeometries()\n\n    def test_model(self):\n        self.prepare_data()\n        w = self.widget\n        w.var_hints = self.var_hints\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        self.send_signal(w.Inputs.data, self.data)\n\n        model = w.varview.model()\n        display = model.index(0).data()\n        self.assertIn(\"x\", display)\n        self.assertIn(\"equal\", display)\n        self.assertIn(\"3\", display)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            display)\n\n        tooltip = model.index(0).data(Qt.ToolTipRole)\n        self.assertIn(\"x\", tooltip)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            tooltip)\n\n        display = model.index(1).data()\n        self.assertIn(\"y\", display)\n        self.assertIn(\"keep\", display)\n\n        self.assertIsNone(model.index(1).data(Qt.ToolTipRole))\n\n        w.var_hints[(\"x\", False)] = VarHint(Methods.EqualWidth, (7, ))\n        del w.discretized_vars[(\"x\", False)]\n        w._update_discretizations()\n        display = model.index(0).data()\n        self.assertIn(\"x\", display)\n        self.assertIn(\"equal\", display)\n        self.assertIn(\"3\", display)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            display)\n\n\nclass TestDefaultDiscModel(GuiTest):\n    def test_counts(self):\n        model = DefaultDiscModel()\n        self.assertEqual(model.rowCount(QModelIndex()), 1)\n        self.assertEqual(model.rowCount(model.index(0)), 0)\n\n        self.assertEqual(model.columnCount(QModelIndex()), 1)\n        self.assertEqual(model.columnCount(model.index(0)), 0)\n\n    def test_data(self):\n        model = DefaultDiscModel()\n        self.assertIn(format_desc(DefaultHint), model.index(0).data())\n        self.assertIsInstance(model.index(0).data(Qt.DecorationRole), QIcon)\n        self.assertIsInstance(model.index(0).data(Qt.ToolTipRole), str)\n\n        hint = VarHint(Methods.FixedWidth, (\"314\", ))\n        model.setData(model.index(0), hint, Qt.UserRole)\n        self.assertIn(format_desc(hint), model.index(0).data())\n        self.assertIsInstance(model.index(0).data(Qt.DecorationRole), QIcon)\n        self.assertIsInstance(model.index(0).data(Qt.ToolTipRole), str)\n\n\n\nclass TestUtils(GuiTest):\n    def test_show_tip(self):\n        w = QWidget()\n        show_tip = IncreasingNumbersListValidator.show_tip\n        show_tip(w, QPoint(100, 100), \"Ha Ha\")\n        app = QApplication.instance()\n        windows = app.topLevelWidgets()\n        label = [tl for tl in windows\n                 if tl.parent() is w and tl.objectName() == \"tip-label\"][0]\n        self.assertTrue(label.isVisible())\n        self.assertTrue(label.text() == \"Ha Ha\")\n\n        show_tip(w, QPoint(100, 100), \"Ha\")\n        self.assertTrue(label.text() == \"Ha\")\n        show_tip(w, QPoint(100, 100), \"\")\n        self.assertFalse(label.isVisible())\n\n    def test_format_desc(self):\n        self.assertEqual(format_desc(VarHint(Methods.MDL, ())),\n                         Options[Methods.MDL].short_desc)\n        self.assertEqual(format_desc(VarHint(Methods.EqualWidth, (\"10\", ))),\n                         Options[Methods.EqualWidth].short_desc.format(10))\n        self.assertEqual(format_desc(None),\n                         Options[Methods.Default].short_desc)\n\n        fwt = Methods.FixedWidthTime\n        desc = Options[fwt].short_desc.format\n        self.assertEqual(format_desc(VarHint(fwt, (\"1\", 0))), desc(\"1\", \"year\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"2\", 0))), desc(\"2\", \"years\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"1\", 2))), desc(\"1\", \"day\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"2\", 2))), desc(\"2\", \"days\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"x\", 2))), desc(\"x\", \"day(s)\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"\", 2))), desc(\"\", \"day(s)\"))\n\n    def test_fixed_width_disc(self):\n        fw = partial(_fixed_width_discretization, None, None)\n        for arg in (\"\", \"5.3.1\", \"abc\", \"-5\", \"0\"):\n            self.assertIsInstance(fw(arg), str)\n\n        with patch(\"Orange.preprocess.discretize.FixedWidth\") as disc:\n            self.assertNotIsInstance(fw(\"5.13\"), str)\n            disc.assert_called_with(5.13, 2)\n\n            self.assertNotIsInstance(fw(\"5\"), str)\n            disc.assert_called_with(5, 0)\n\n        with patch(\"Orange.preprocess.discretize.FixedWidth\",\n                   side_effect=TooManyIntervals):\n            self.assertIsInstance(fw(\"42\"), str)\n\n    def test_fixed_time_width_disc(self):\n        ftw = partial(_fixed_time_width_discretization, None, None)\n\n        for arg in (\"\", \"5.3.1\", \"5.3\", \"abc\", \"-5\", \"0\"):\n            self.assertIsInstance(ftw(arg, 1), str)\n\n        with patch(\"Orange.preprocess.discretize.FixedTimeWidth\") as disc:\n            self.assertNotIsInstance(ftw(\"5\", 2), str)\n            disc.assert_called_with(5, 2)\n\n            self.assertNotIsInstance(ftw(\"5\", 3), str)\n            disc.assert_called_with(35, 2)\n\n            self.assertNotIsInstance(ftw(\"5\", 4), str)\n            disc.assert_called_with(5, 3)\n\n        with patch(\"Orange.preprocess.discretize.FixedTimeWidth\",\n                   side_effect=TooManyIntervals):\n            self.assertIsInstance(ftw(\"42\", 3), str)\n\n    def test_custom_discretization(self):\n        cd = partial(_custom_discretization, None, None)\n\n        for arg in (\"\", \"4 5\", \"2, 1, 5\", \"1, foo, 13\"):\n            self.assertIsInstance(cd(arg), str)\n\n        with patch(\"Orange.preprocess.discretize.Discretizer.\"\n                   \"create_discretized_var\") as disc:\n            cd(\"1, 1.25, 1.5, 4\")\n            disc.assert_called_with(None, [1, 1.25, 1.5, 4])\n\n    def test_mdl_discretization(self):\n        mdl = _mdl_discretization\n        data = Table(\"iris\")[::10]\n        var = data.domain[0]\n        with patch(\"Orange.preprocess.discretize.EntropyMDL\") as mdldisc:\n            mdl(data, var)\n            mdldisc.return_value.assert_called_with(data, var)\n            mdldisc.reset_mock()\n\n            data = data[:, :4]\n            self.assertIsInstance(mdl(data, var), str)\n            mdldisc.assert_not_called()\n\n            data = data.transform(Domain(data.domain[:3], data.domain[3]))\n            self.assertIsInstance(mdl(data, var), str)\n            mdldisc.assert_not_called()\n\n    def test_var_key(self):\n        self.assertEqual(variable_key(ContinuousVariable(\"foo\")),\n                         (\"foo\", False))\n        self.assertEqual(variable_key(TimeVariable(\"bar\")),\n                         (\"bar\", True))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "new_file_content": "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring,unsubscriptable-object,protected-access\nimport unittest\nfrom functools import partial\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QPoint, Qt, QModelIndex\nfrom AnyQt.QtWidgets import QWidget, QApplication, QStyleOptionViewItem\nfrom AnyQt.QtGui import QIcon\n\nfrom orangewidget.settings import Context\n\nfrom Orange.data import Table, ContinuousVariable, TimeVariable, Domain\nfrom Orange.preprocess.discretize import TooManyIntervals\nfrom Orange.widgets.data.owdiscretize import OWDiscretize, \\\n    IncreasingNumbersListValidator, VarHint, Methods, DefaultKey, \\\n    _fixed_width_discretization, _fixed_time_width_discretization, \\\n    _custom_discretization, variable_key, Options, DefaultHint, \\\n    _mdl_discretization, ListViewSearch, format_desc, DefaultDiscModel\nfrom Orange.widgets.tests.base import WidgetTest, GuiTest\nfrom Orange.widgets.utils.itemmodels import select_rows\n\n\nclass DataMixin:\n    def prepare_data(self):\n        self.domain = Domain([ContinuousVariable(\"x\"),\n                              ContinuousVariable(\"y\"),\n                              ContinuousVariable(\"z\"),\n                              TimeVariable(\"t\"),\n                              TimeVariable(\"u\")])\n        self.data = Table.from_numpy(self.domain, np.arange(20).reshape(4, 5))\n        self.var_hints = {\n            DefaultKey: VarHint(Methods.Keep, ()),\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.Keep, ()),\n            (\"z\", False): VarHint(Methods.Remove, ()),\n            (\"t\", True): VarHint(Methods.Binning, (2, ))\n        }\n        # Copy the following line to tests, for reference:\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n\nclass TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})\n\n\nclass TestValidator(unittest.TestCase):\n    def test_validate(self):\n        v = IncreasingNumbersListValidator()\n        self.assertEqual(v.validate(\"\", 0), (v.Intermediate, '', 0))\n        self.assertEqual(v.validate(\"1\", 1), (v.Acceptable, '1', 1))\n        self.assertEqual(v.validate(\",\", 0), (v.Intermediate, ',', 0))\n        self.assertEqual(v.validate(\"-\", 0), (v.Intermediate, '-', 0))\n        self.assertEqual(v.validate(\"1,,\", 1), (v.Intermediate, '1,,', 1))\n        self.assertEqual(v.validate(\"1,a,\", 1), (v.Invalid, '1,a,', 3))\n        self.assertEqual(v.validate(\"a\", 1), (v.Invalid, 'a', 1))\n        self.assertEqual(v.validate(\"1,1\", 0), (v.Intermediate, '1,1', 0))\n        self.assertEqual(v.validate(\"1,12\", 0), (v.Acceptable, '1,12', 0))\n\n        self.assertEqual(v.validate(\"1, 2 \", 5), (v.Intermediate, \"1, 2, \", 6))\n\n\nclass TestModels(WidgetTest, DataMixin):\n    def setUp(self):\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n    def test_delegate(self):\n        self.prepare_data()\n        w = self.widget\n        w.var_hints = self.var_hints\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        self.send_signal(w.Inputs.data, self.data)\n\n        model = w.varview.model()\n        delegate: ListViewSearch.DiscDelegate = w.varview.itemDelegate()\n        option = QStyleOptionViewItem()\n        delegate.initStyleOption(option, model.index(0))\n        self.assertTrue(option.font.bold())\n\n        option = QStyleOptionViewItem()\n        delegate.initStyleOption(option, model.index(4))\n        self.assertFalse(option.font.bold())\n\n    def test_layout(self):\n        # Not much to test, just don't crash\n        self.widget.varview.updateGeometries()\n\n    def test_model(self):\n        self.prepare_data()\n        w = self.widget\n        w.var_hints = self.var_hints\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        self.send_signal(w.Inputs.data, self.data)\n\n        model = w.varview.model()\n        display = model.index(0).data()\n        self.assertIn(\"x\", display)\n        self.assertIn(\"equal\", display)\n        self.assertIn(\"3\", display)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            display)\n\n        tooltip = model.index(0).data(Qt.ToolTipRole)\n        self.assertIn(\"x\", tooltip)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            tooltip)\n\n        display = model.index(1).data()\n        self.assertIn(\"y\", display)\n        self.assertIn(\"keep\", display)\n\n        self.assertIsNone(model.index(1).data(Qt.ToolTipRole))\n\n        w.var_hints[(\"x\", False)] = VarHint(Methods.EqualWidth, (7, ))\n        del w.discretized_vars[(\"x\", False)]\n        w._update_discretizations()\n        display = model.index(0).data()\n        self.assertIn(\"x\", display)\n        self.assertIn(\"equal\", display)\n        self.assertIn(\"3\", display)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            display)\n\n\nclass TestDefaultDiscModel(GuiTest):\n    def test_counts(self):\n        model = DefaultDiscModel()\n        self.assertEqual(model.rowCount(QModelIndex()), 1)\n        self.assertEqual(model.rowCount(model.index(0)), 0)\n\n        self.assertEqual(model.columnCount(QModelIndex()), 1)\n        self.assertEqual(model.columnCount(model.index(0)), 0)\n\n    def test_data(self):\n        model = DefaultDiscModel()\n        self.assertIn(format_desc(DefaultHint), model.index(0).data())\n        self.assertIsInstance(model.index(0).data(Qt.DecorationRole), QIcon)\n        self.assertIsInstance(model.index(0).data(Qt.ToolTipRole), str)\n\n        hint = VarHint(Methods.FixedWidth, (\"314\", ))\n        model.setData(model.index(0), hint, Qt.UserRole)\n        self.assertIn(format_desc(hint), model.index(0).data())\n        self.assertIsInstance(model.index(0).data(Qt.DecorationRole), QIcon)\n        self.assertIsInstance(model.index(0).data(Qt.ToolTipRole), str)\n\n\n\nclass TestUtils(GuiTest):\n    def test_show_tip(self):\n        w = QWidget()\n        show_tip = IncreasingNumbersListValidator.show_tip\n        show_tip(w, QPoint(100, 100), \"Ha Ha\")\n        app = QApplication.instance()\n        windows = app.topLevelWidgets()\n        label = [tl for tl in windows\n                 if tl.parent() is w and tl.objectName() == \"tip-label\"][0]\n        self.assertTrue(label.isVisible())\n        self.assertTrue(label.text() == \"Ha Ha\")\n\n        show_tip(w, QPoint(100, 100), \"Ha\")\n        self.assertTrue(label.text() == \"Ha\")\n        show_tip(w, QPoint(100, 100), \"\")\n        self.assertFalse(label.isVisible())\n\n    def test_format_desc(self):\n        self.assertEqual(format_desc(VarHint(Methods.MDL, ())),\n                         Options[Methods.MDL].short_desc)\n        self.assertEqual(format_desc(VarHint(Methods.EqualWidth, (\"10\", ))),\n                         Options[Methods.EqualWidth].short_desc.format(10))\n        self.assertEqual(format_desc(None),\n                         Options[Methods.Default].short_desc)\n\n        fwt = Methods.FixedWidthTime\n        desc = Options[fwt].short_desc.format\n        self.assertEqual(format_desc(VarHint(fwt, (\"1\", 0))), desc(\"1\", \"year\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"2\", 0))), desc(\"2\", \"years\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"1\", 2))), desc(\"1\", \"day\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"2\", 2))), desc(\"2\", \"days\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"x\", 2))), desc(\"x\", \"day(s)\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"\", 2))), desc(\"\", \"day(s)\"))\n\n    def test_fixed_width_disc(self):\n        fw = partial(_fixed_width_discretization, None, None)\n        for arg in (\"\", \"5.3.1\", \"abc\", \"-5\", \"0\"):\n            self.assertIsInstance(fw(arg), str)\n\n        with patch(\"Orange.preprocess.discretize.FixedWidth\") as disc:\n            self.assertNotIsInstance(fw(\"5.13\"), str)\n            disc.assert_called_with(5.13, 2)\n\n            self.assertNotIsInstance(fw(\"5\"), str)\n            disc.assert_called_with(5, 0)\n\n        with patch(\"Orange.preprocess.discretize.FixedWidth\",\n                   side_effect=TooManyIntervals):\n            self.assertIsInstance(fw(\"42\"), str)\n\n    def test_fixed_time_width_disc(self):\n        ftw = partial(_fixed_time_width_discretization, None, None)\n\n        for arg in (\"\", \"5.3.1\", \"5.3\", \"abc\", \"-5\", \"0\"):\n            self.assertIsInstance(ftw(arg, 1), str)\n\n        with patch(\"Orange.preprocess.discretize.FixedTimeWidth\") as disc:\n            self.assertNotIsInstance(ftw(\"5\", 2), str)\n            disc.assert_called_with(5, 2)\n\n            self.assertNotIsInstance(ftw(\"5\", 3), str)\n            disc.assert_called_with(35, 2)\n\n            self.assertNotIsInstance(ftw(\"5\", 4), str)\n            disc.assert_called_with(5, 3)\n\n        with patch(\"Orange.preprocess.discretize.FixedTimeWidth\",\n                   side_effect=TooManyIntervals):\n            self.assertIsInstance(ftw(\"42\", 3), str)\n\n    def test_custom_discretization(self):\n        cd = partial(_custom_discretization, None, None)\n\n        for arg in (\"\", \"4 5\", \"2, 1, 5\", \"1, foo, 13\"):\n            self.assertIsInstance(cd(arg), str)\n\n        with patch(\"Orange.preprocess.discretize.Discretizer.\"\n                   \"create_discretized_var\") as disc:\n            cd(\"1, 1.25, 1.5, 4\")\n            disc.assert_called_with(None, [1, 1.25, 1.5, 4])\n\n    def test_mdl_discretization(self):\n        mdl = _mdl_discretization\n        data = Table(\"iris\")[::10]\n        var = data.domain[0]\n        with patch(\"Orange.preprocess.discretize.EntropyMDL\") as mdldisc:\n            mdl(data, var)\n            mdldisc.return_value.assert_called_with(data, var)\n            mdldisc.reset_mock()\n\n            data = data[:, :4]\n            self.assertIsInstance(mdl(data, var), str)\n            mdldisc.assert_not_called()\n\n            data = data.transform(Domain(data.domain[:3], data.domain[3]))\n            self.assertIsInstance(mdl(data, var), str)\n            mdldisc.assert_not_called()\n\n    def test_var_key(self):\n        self.assertEqual(variable_key(ContinuousVariable(\"foo\")),\n                         (\"foo\", False))\n        self.assertEqual(variable_key(TimeVariable(\"bar\")),\n                         (\"bar\", True))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n",
            "header": {
                "file": {
                    "path": "Orange/widgets/data/tests/test_owdiscretize.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "f69cbd485",
                "new_commit_hash": "2fb0979c2",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Orange/widgets/data/tests/test_owdiscretize.py"
            },
            "plus_file": {
                "path": "b/Orange/widgets/data/tests/test_owdiscretize.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 198,
                            "length": 6
                        },
                        "new_range": {
                            "start": 198,
                            "length": 7
                        },
                        "section": "class TestOWDiscretize(WidgetTest, DataMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.copy_to_custom.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(w.width_line.text(), \"10\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        select_rows(w.varview, (1, 2))",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "method",
                            "name": "TestOWDiscretize.test_change_selection_update_interface",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 185,
                            "end_lineno": 241,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                                "type": "class",
                                "name": "TestOWDiscretize",
                                "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 44,
                                "end_lineno": 467,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "class",
                            "name": "TestOWDiscretize",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 44,
                            "end_lineno": 467,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 205,
                            "length": 6
                        },
                        "new_range": {
                            "start": 206,
                            "length": 7
                        },
                        "section": "class TestOWDiscretize(WidgetTest, DataMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.copy_to_custom.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        select_rows(w.varview, (2, 4))",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "method",
                            "name": "TestOWDiscretize.test_change_selection_update_interface",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 185,
                            "end_lineno": 241,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                                "type": "class",
                                "name": "TestOWDiscretize",
                                "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 44,
                                "end_lineno": 467,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "class",
                            "name": "TestOWDiscretize",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 44,
                            "end_lineno": 467,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 217,
                            "length": 6
                        },
                        "new_range": {
                            "start": 219,
                            "length": 7
                        },
                        "section": "class TestOWDiscretize(WidgetTest, DataMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(w.copy_to_custom.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        select_rows(w.varview.default_view, (0, ))",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "method",
                            "name": "TestOWDiscretize.test_change_selection_update_interface",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 185,
                            "end_lineno": 241,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                                "type": "class",
                                "name": "TestOWDiscretize",
                                "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 44,
                                "end_lineno": 467,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "class",
                            "name": "TestOWDiscretize",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 44,
                            "end_lineno": 467,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 224,
                            "length": 6
                        },
                        "new_range": {
                            "start": 227,
                            "length": 7
                        },
                        "section": "class TestOWDiscretize(WidgetTest, DataMixin):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertFalse(w.copy_to_custom.isEnabled())",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())",
                                "type": "context"
                            },
                            {
                                "content": "        w._check_button(Methods.FixedWidth, True)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "method",
                            "name": "TestOWDiscretize.test_change_selection_update_interface",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 185,
                            "end_lineno": 241,
                            "parent": {
                                "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                                "type": "class",
                                "name": "TestOWDiscretize",
                                "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 44,
                                "end_lineno": 467,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Orange/widgets/data/tests/test_owdiscretize.py",
                            "type": "class",
                            "name": "TestOWDiscretize",
                            "content": "class TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 44,
                            "end_lineno": 467,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "86b10c09c04fb66f04454f98826eb67de5e3117c^",
    "new_commit_hash": "86b10c09c04fb66f04454f98826eb67de5e3117c",
    "commit_message": "OWDiscretize: Disable copy-to-custom when default is selected",
    "commit_date": "2022-04-10T23:42:07+02:00",
    "metadata": {}
}
{
    "repo_name": "orange3",
    "new_commit_hash": "86b10c09c04fb66f04454f98826eb67de5e3117c",
    "test_file_codes": [
        "# Test methods with long descriptive names can omit docstrings\n# pylint: disable=missing-docstring,unsubscriptable-object,protected-access\nimport unittest\nfrom functools import partial\nfrom unittest.mock import patch, Mock\n\nimport numpy as np\n\nfrom AnyQt.QtCore import QPoint, Qt, QModelIndex\nfrom AnyQt.QtWidgets import QWidget, QApplication, QStyleOptionViewItem\nfrom AnyQt.QtGui import QIcon\n\nfrom orangewidget.settings import Context\n\nfrom Orange.data import Table, ContinuousVariable, TimeVariable, Domain\nfrom Orange.preprocess.discretize import TooManyIntervals\nfrom Orange.widgets.data.owdiscretize import OWDiscretize, \\\n    IncreasingNumbersListValidator, VarHint, Methods, DefaultKey, \\\n    _fixed_width_discretization, _fixed_time_width_discretization, \\\n    _custom_discretization, variable_key, Options, DefaultHint, \\\n    _mdl_discretization, ListViewSearch, format_desc, DefaultDiscModel\nfrom Orange.widgets.tests.base import WidgetTest, GuiTest\nfrom Orange.widgets.utils.itemmodels import select_rows\n\n\nclass DataMixin:\n    def prepare_data(self):\n        self.domain = Domain([ContinuousVariable(\"x\"),\n                              ContinuousVariable(\"y\"),\n                              ContinuousVariable(\"z\"),\n                              TimeVariable(\"t\"),\n                              TimeVariable(\"u\")])\n        self.data = Table.from_numpy(self.domain, np.arange(20).reshape(4, 5))\n        self.var_hints = {\n            DefaultKey: VarHint(Methods.Keep, ()),\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.Keep, ()),\n            (\"z\", False): VarHint(Methods.Remove, ()),\n            (\"t\", True): VarHint(Methods.Binning, (2, ))\n        }\n        # Copy the following line to tests, for reference:\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n\nclass TestOWDiscretize(WidgetTest, DataMixin):\n    def setUp(self):\n        super().setUp()\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n\n    def test_empty_data(self):\n        data = Table(\"iris\")\n        widget = self.widget\n        self.send_signal(self.widget.Inputs.data,\n                         Table.from_domain(data.domain))\n        for m in range(len(Methods)):\n            widget.var_hints = {DefaultKey: VarHint(m, ())}\n            widget.commit.now()\n            self.assertIsNotNone(self.get_output(widget.Outputs.data))\n\n    def test_report(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n        self.send_signal(w.Inputs.data, data)\n\n        self.widget.send_report()\n\n    def test_all(self):\n        data = Table(\"brown-selected\")\n\n        w = self.create_widget(\n            OWDiscretize,\n            {\"var_hints\":\n                {None: VarHint(Methods.EqualFreq, (3,)),\n                 ('alpha 0', False): VarHint(Methods.Keep, ()),\n                 ('alpha 7', False): VarHint(Methods.Remove, ()),\n                 ('alpha 14', False): VarHint(Methods.Binning, (2, )),\n                 ('alpha 21', False): VarHint(Methods.FixedWidth, (\"0.05\", )),\n                 ('alpha 28', False): VarHint(Methods.EqualFreq, (4, )),\n                 ('alpha 35', False): VarHint(Methods.MDL, ()),\n                 ('alpha 42', False): VarHint(Methods.Custom, (\"0, 0.125\", )),\n                 ('alpha 49', False): VarHint(Methods.MDL, ())},\n             \"__version__\": 3})\n\n        self.send_signal(w.Inputs.data, data)\n\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertEqual(w.varview.default_view.model().hint,\n                         VarHint(Methods.EqualFreq, (3, )))\n\n        out = self.get_output(w.Outputs.data)\n        dom = out.domain\n        self.assertIsInstance(dom[\"alpha 0\"], ContinuousVariable)\n        self.assertNotIn(\"alpha 7\", dom)\n        self.assertEqual(dom[\"alpha 14\"].values, ('< 0', '≥ 0'))\n        self.assertEqual(dom[\"alpha 21\"].values,\n                         ('< -0.15', \"-0.15 - -0.10\", \"-0.10 - -0.05\",\n                          \"-0.05 - 0.00\", \"0.00 - 0.05\", \"0.05 - 0.10\",\n                          '≥ 0.10'))\n        self.assertEqual(len(dom[\"alpha 28\"].values), 4)\n        self.assertNotIn(\"alpha 35\", dom)  # removed by MDL\n        self.assertEqual(dom[\"alpha 42\"].values, ('< 0', '0 - 0.125', '≥ 0.125'))\n        self.assertEqual(len(dom[\"alpha 49\"].values), 2)\n\n        self.send_signal(w.Inputs.data, None)\n        self.assertIsNone(self.get_output(w.Outputs.data))\n        self.assertIsNone(w.data)\n        self.assertEqual(w.discretized_vars, {})\n        self.assertEqual(len(w.varview.model()), 0)\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertIsNotNone(self.get_output(w.Outputs.data))\n        w.button_group.button(Methods.MDL).setChecked(True)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data[:, 0])\n        self.assertFalse(w.button_group.button(Methods.MDL).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.MDL).isChecked())\n\n        self.send_signal(w.Inputs.data, data)\n        self.assertTrue(w.button_group.button(Methods.MDL).isEnabled())\n\n    def test_get_values(self):\n        w = self.widget\n\n        w.binning_spin.setValue(5)\n        w.width_line.setText(\"6\")\n        w.width_time_line.setText(\"7\")\n        w.width_time_unit.setCurrentIndex(1)\n        w.freq_spin.setValue(8)\n        w.width_spin.setValue(9)\n        w.threshold_line.setText(\"1, 2, 3, 4, 5\")\n\n        self.assertEqual(w._get_values(Methods.Keep), ())\n        self.assertEqual(w._get_values(Methods.Remove), ())\n        self.assertEqual(w._get_values(Methods.Binning), (5, ))\n        self.assertEqual(w._get_values(Methods.FixedWidth), (\"6\", ))\n        self.assertEqual(w._get_values(Methods.FixedWidthTime), (\"7\", 1))\n        self.assertEqual(w._get_values(Methods.EqualFreq), (8, ))\n        self.assertEqual(w._get_values(Methods.EqualWidth), (9, ))\n        self.assertEqual(w._get_values(Methods.MDL), ())\n        self.assertEqual(w._get_values(Methods.Custom), (\"1, 2, 3, 4, 5\", ))\n\n    def test_set_values(self):\n        w = self.widget\n\n        w._set_values(Methods.Keep, ())\n        w._set_values(Methods.Remove, ())\n        w._set_values(Methods.Binning, (5,))\n        w._set_values(Methods.FixedWidth, (\"6\",))\n        w._set_values(Methods.FixedWidthTime, (\"7\", 1))\n        w._set_values(Methods.EqualFreq, (8,))\n        w._set_values(Methods.EqualWidth, (9,))\n        w._set_values(Methods.MDL, ())\n        w._set_values(Methods.Custom, (\"1, 2, 3, 4, 5\",))\n\n        self.assertEqual(w.binning_spin.value(), 5)\n        self.assertEqual(w.width_line.text(), \"6\")\n        self.assertEqual(w.width_time_line.text(), \"7\")\n        self.assertEqual(w.width_time_unit.currentIndex(), 1)\n        self.assertEqual(w.freq_spin.value(), 8)\n        self.assertEqual(w.width_spin.value(), 9)\n        self.assertEqual(w.threshold_line.text(), \"1, 2, 3, 4, 5\")\n\n    def test_varkeys_for_selection(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        select_rows(w.varview, (0, 4))\n        self.assertEqual(w.varkeys_for_selection(), [(\"x\", False), (\"u\", True)])\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n\n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n\n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n\n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n\n        select_rows(w.varview.default_view, (0, ))\n        self.assertEqual(len(w.varview.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertFalse(w.copy_to_custom.isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Default).isEnabled())\n        w._check_button(Methods.FixedWidth, True)\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n\n        select_rows(w.varview, (3, ))\n        self.assertEqual(len(w.varview.default_view.selectionModel().selectedIndexes()), 0)\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Default).isEnabled())\n\n    def test_update_hints(self):\n        w = self.widget\n        update_disc = w._update_discretizations\n        w._update_discretizations = Mock()\n        w.width_line.setText(\"10\")\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"y\", False): VarHint(Methods.EqualFreq, (3, )),\n            (\"z\", False): VarHint(Methods.EqualFreq, (4, )),\n            (\"t\", True): VarHint(Methods.Binning, (5, ))\n        }\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n\n        select_rows(w.varview, (0, ))\n        w.button_group.button(Methods.Default).click()\n        self.assertNotIn((\"x\", False), w.var_hints)\n        # Check that \"x\" is invalidated\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n\n        select_rows(w.varview, (0, 1))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"10\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        w.width_line.setText(\"5\")\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.FixedWidth, (\"5\", )))\n        # Check that \"x\" and \"y\" are invalidated\n        self.assertEqual(len(w.discretized_vars), 3)\n        self.assertNotIn((\"x\", False), w.discretized_vars)\n        self.assertNotIn((\"y\", False), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"x\", False), w.discretized_vars)\n        self.assertIn((\"y\", False), w.discretized_vars)\n\n        select_rows(w.varview.default_view, (0, ))\n        w.button_group.button(Methods.FixedWidth).click()\n        self.assertEqual(len(w.discretized_vars), 4)\n        self.assertNotIn((\"u\", True), w.discretized_vars)\n        update_disc()\n        self.assertEqual(len(w.discretized_vars), 5)\n        self.assertIn((\"u\", True), w.discretized_vars)\n\n    def test_discretize_var(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n\n        x = self.data.domain[\"x\"]\n        t = self.data.domain[\"t\"]\n\n        s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidthTime, (\"10\", 0)))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, x)\n\n        s, dvar = w._discretize_var(t, VarHint(Methods.FixedWidth, (\"10\", )))\n        self.assertIn(\"keep\", s)\n        self.assertIs(dvar, t)\n\n        try:\n            Options[42] = Mock()\n\n            # Errored\n            # Unit test - mocked function\n            Options[42].function = lambda *_: \"foo error\"\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"foo error\", s)\n            self.assertIsNone(dvar)\n            # Real error\n            s, dvar = w._discretize_var(t, VarHint(Methods.MDL, ()))\n            self.assertIn(\"<\", s)\n            self.assertIsNone(dvar)\n\n            # Removed attribute\n            Options[42].function = lambda *_: None\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n            # Really removed\n            s, dvar = w._discretize_var(t, VarHint(Methods.Remove, ()))\n            self.assertEqual(\"\", s)\n            self.assertIsNone(dvar)\n\n            # No intervals\n            var = Mock(compute_value=Mock(points=[]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n            s, dvar = w._discretize_var(x, VarHint(Methods.FixedWidth, (\"1000\", )))\n            self.assertIn(\"removed\", s)\n            self.assertIsNone(dvar)\n\n            # All fine\n            var = Mock(compute_value=Mock(points=[1, 2, 3]))\n            Options[42].function = lambda *_: var\n            s, dvar = w._discretize_var(t, VarHint(42, ()))\n            self.assertIn(\"1, 2, 3\", s)\n            self.assertIs(dvar, var)\n            s, dvar = w._discretize_var(x, VarHint(Methods.EqualWidth, (3, )))\n            self.assertEqual(dvar.compute_value.points, [5, 10])\n\n        finally:\n            del Options[42]\n\n    def test_update_discretizations(self):\n        w = self.widget\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        w.var_hints = self.var_hints\n        y, t, u = map(self.domain.__getitem__, \"ytu\")\n\n        # no data: do nothing, but don't crash\n        w._update_discretizations()\n\n        self.send_signal(w.Inputs.data, self.data)\n        d = w.discretized_vars\n        self.assertEqual(len(d), 5)\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"y\", False)], y)\n        self.assertIsNone(d[(\"z\", False)])\n        self.assertIsNot(d[(\"t\", True)], t)\n        self.assertIsNotNone(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        d[(\"t\", True)] = t\n        del d[(\"x\", False)]\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertEqual(len(d[(\"x\", False)].values), 3)\n        self.assertIs(d[(\"t\", True)], t)\n        self.assertIs(d[(\"u\", True)], u)\n\n        w.var_hints[None] = VarHint(Methods.Remove, ())\n        del d[(\"u\", True)]\n        w._update_discretizations()\n        self.assertIsNone(d[(\"u\", True)])\n\n    def test_copy_to_manual(self):\n        w = self.widget\n        w.var_hints = { DefaultKey: VarHint(Methods.EqualFreq, (5, )) }\n        self.send_signal(w.Inputs.data, self.data)\n        w.button_group.button(Methods.MDL).setChecked(True)\n\n        select_rows(w.varview, (0, 2))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertFalse(any(w.button_group.button(i).isChecked()\n                             for i in Methods))\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertNotIn((\"y\", False), w.var_hints)\n\n        select_rows(w.varview, (1, ))\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        w.copy_to_custom.click()\n        self.assertTrue(w.button_group.button(Methods.Custom).isChecked())\n        self.assertEqual(w.var_hints[(\"x\", False)],\n                         VarHint(Methods.Custom, ('2.5, 7.5, 12.5', )))\n        self.assertEqual(w.var_hints[(\"z\", False)],\n                         VarHint(Methods.Custom, ('4.5, 9.5, 14.5', )))\n        self.assertEqual(w.var_hints[(\"y\", False)],\n                         VarHint(Methods.Custom, ('3.5, 8.5, 13.5', )))\n        self.assertEqual(w.threshold_line.text(), '3.5, 8.5, 13.5')\n\n        select_rows(w.varview, (1, 4))\n        w.copy_to_custom.click()\n        self.assertNotIn((\"u\", False), w.var_hints)\n\n    def test_migration_2_3(self):\n        # Obsolete, don't want to cause confusion by public import\n        # pylint: disable=import-outside-toplevel\n        from Orange.widgets.data.owdiscretize import \\\n            Default, EqualFreq, Leave, Custom, MDL, EqualWidth, DState\n        context_values = {\n            'saved_var_states':\n                ({(2, 'age'): DState(method=Leave()),\n                  (2, 'rest SBP'): DState(method=EqualWidth(k=4)),\n                  (2, 'cholesterol'): DState(method=EqualFreq(k=6)),\n                  (4, 'max HR'): DState(\n                      method=Custom(points=(1.0, 2.0, 3.0))),\n                  (2, 'ST by exercise'): DState(method=MDL()),\n                  (2, 'major vessels colored'):\n                      DState(method=Default(method=EqualFreq(k=3)))}, -2),\n            '__version__': 2}\n\n        settings = {'autosend': True, 'controlAreaVisible': True,\n                    'default_cutpoints': (), 'default_k': 3,\n                    'default_method_name': 'EqualFreq',\n                    '__version__': 2,\n                    \"context_settings\": [Context(values=context_values)]}\n\n        OWDiscretize.migrate_settings(settings, 2)\n        self.assertNotIn(\"default_method_name\", settings)\n        self.assertNotIn(\"default_k\", settings)\n        self.assertNotIn(\"default_cutpoints\", settings)\n        self.assertNotIn(\"context_settings\", settings)\n        self.assertEqual(\n            settings[\"var_hints\"],\n            {None: VarHint(Methods.EqualFreq, (3,)),\n             ('ST by exercise', False): VarHint(Methods.MDL, ()),\n             ('age', False): VarHint(Methods.Keep, ()),\n             ('cholesterol', False): VarHint(Methods.EqualFreq, (6,)),\n             ('max HR', True): VarHint(Methods.Custom, (('1, 2, 3'),)),\n             ('rest SBP', False): VarHint(Methods.EqualWidth, (4,))})\n\n\nclass TestValidator(unittest.TestCase):\n    def test_validate(self):\n        v = IncreasingNumbersListValidator()\n        self.assertEqual(v.validate(\"\", 0), (v.Intermediate, '', 0))\n        self.assertEqual(v.validate(\"1\", 1), (v.Acceptable, '1', 1))\n        self.assertEqual(v.validate(\",\", 0), (v.Intermediate, ',', 0))\n        self.assertEqual(v.validate(\"-\", 0), (v.Intermediate, '-', 0))\n        self.assertEqual(v.validate(\"1,,\", 1), (v.Intermediate, '1,,', 1))\n        self.assertEqual(v.validate(\"1,a,\", 1), (v.Invalid, '1,a,', 3))\n        self.assertEqual(v.validate(\"a\", 1), (v.Invalid, 'a', 1))\n        self.assertEqual(v.validate(\"1,1\", 0), (v.Intermediate, '1,1', 0))\n        self.assertEqual(v.validate(\"1,12\", 0), (v.Acceptable, '1,12', 0))\n\n        self.assertEqual(v.validate(\"1, 2 \", 5), (v.Intermediate, \"1, 2, \", 6))\n\n\nclass TestModels(WidgetTest, DataMixin):\n    def setUp(self):\n        self.prepare_data()\n        self.widget = self.create_widget(OWDiscretize)\n\n    def test_delegate(self):\n        self.prepare_data()\n        w = self.widget\n        w.var_hints = self.var_hints\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        self.send_signal(w.Inputs.data, self.data)\n\n        model = w.varview.model()\n        delegate: ListViewSearch.DiscDelegate = w.varview.itemDelegate()\n        option = QStyleOptionViewItem()\n        delegate.initStyleOption(option, model.index(0))\n        self.assertTrue(option.font.bold())\n\n        option = QStyleOptionViewItem()\n        delegate.initStyleOption(option, model.index(4))\n        self.assertFalse(option.font.bold())\n\n    def test_layout(self):\n        # Not much to test, just don't crash\n        self.widget.varview.updateGeometries()\n\n    def test_model(self):\n        self.prepare_data()\n        w = self.widget\n        w.var_hints = self.var_hints\n        # Def: Keep, x: EqFreq 3, y: Keep, z: Remove, t (time): Bin 2, u (time):\n        self.send_signal(w.Inputs.data, self.data)\n\n        model = w.varview.model()\n        display = model.index(0).data()\n        self.assertIn(\"x\", display)\n        self.assertIn(\"equal\", display)\n        self.assertIn(\"3\", display)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            display)\n\n        tooltip = model.index(0).data(Qt.ToolTipRole)\n        self.assertIn(\"x\", tooltip)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            tooltip)\n\n        display = model.index(1).data()\n        self.assertIn(\"y\", display)\n        self.assertIn(\"keep\", display)\n\n        self.assertIsNone(model.index(1).data(Qt.ToolTipRole))\n\n        w.var_hints[(\"x\", False)] = VarHint(Methods.EqualWidth, (7, ))\n        del w.discretized_vars[(\"x\", False)]\n        w._update_discretizations()\n        display = model.index(0).data()\n        self.assertIn(\"x\", display)\n        self.assertIn(\"equal\", display)\n        self.assertIn(\"3\", display)\n        self.assertIn(\n            str(w.discretized_vars[(\"x\", False)].compute_value.points[0])[:3],\n            display)\n\n\nclass TestDefaultDiscModel(GuiTest):\n    def test_counts(self):\n        model = DefaultDiscModel()\n        self.assertEqual(model.rowCount(QModelIndex()), 1)\n        self.assertEqual(model.rowCount(model.index(0)), 0)\n\n        self.assertEqual(model.columnCount(QModelIndex()), 1)\n        self.assertEqual(model.columnCount(model.index(0)), 0)\n\n    def test_data(self):\n        model = DefaultDiscModel()\n        self.assertIn(format_desc(DefaultHint), model.index(0).data())\n        self.assertIsInstance(model.index(0).data(Qt.DecorationRole), QIcon)\n        self.assertIsInstance(model.index(0).data(Qt.ToolTipRole), str)\n\n        hint = VarHint(Methods.FixedWidth, (\"314\", ))\n        model.setData(model.index(0), hint, Qt.UserRole)\n        self.assertIn(format_desc(hint), model.index(0).data())\n        self.assertIsInstance(model.index(0).data(Qt.DecorationRole), QIcon)\n        self.assertIsInstance(model.index(0).data(Qt.ToolTipRole), str)\n\n\n\nclass TestUtils(GuiTest):\n    def test_show_tip(self):\n        w = QWidget()\n        show_tip = IncreasingNumbersListValidator.show_tip\n        show_tip(w, QPoint(100, 100), \"Ha Ha\")\n        app = QApplication.instance()\n        windows = app.topLevelWidgets()\n        label = [tl for tl in windows\n                 if tl.parent() is w and tl.objectName() == \"tip-label\"][0]\n        self.assertTrue(label.isVisible())\n        self.assertTrue(label.text() == \"Ha Ha\")\n\n        show_tip(w, QPoint(100, 100), \"Ha\")\n        self.assertTrue(label.text() == \"Ha\")\n        show_tip(w, QPoint(100, 100), \"\")\n        self.assertFalse(label.isVisible())\n\n    def test_format_desc(self):\n        self.assertEqual(format_desc(VarHint(Methods.MDL, ())),\n                         Options[Methods.MDL].short_desc)\n        self.assertEqual(format_desc(VarHint(Methods.EqualWidth, (\"10\", ))),\n                         Options[Methods.EqualWidth].short_desc.format(10))\n        self.assertEqual(format_desc(None),\n                         Options[Methods.Default].short_desc)\n\n        fwt = Methods.FixedWidthTime\n        desc = Options[fwt].short_desc.format\n        self.assertEqual(format_desc(VarHint(fwt, (\"1\", 0))), desc(\"1\", \"year\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"2\", 0))), desc(\"2\", \"years\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"1\", 2))), desc(\"1\", \"day\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"2\", 2))), desc(\"2\", \"days\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"x\", 2))), desc(\"x\", \"day(s)\"))\n        self.assertEqual(format_desc(VarHint(fwt, (\"\", 2))), desc(\"\", \"day(s)\"))\n\n    def test_fixed_width_disc(self):\n        fw = partial(_fixed_width_discretization, None, None)\n        for arg in (\"\", \"5.3.1\", \"abc\", \"-5\", \"0\"):\n            self.assertIsInstance(fw(arg), str)\n\n        with patch(\"Orange.preprocess.discretize.FixedWidth\") as disc:\n            self.assertNotIsInstance(fw(\"5.13\"), str)\n            disc.assert_called_with(5.13, 2)\n\n            self.assertNotIsInstance(fw(\"5\"), str)\n            disc.assert_called_with(5, 0)\n\n        with patch(\"Orange.preprocess.discretize.FixedWidth\",\n                   side_effect=TooManyIntervals):\n            self.assertIsInstance(fw(\"42\"), str)\n\n    def test_fixed_time_width_disc(self):\n        ftw = partial(_fixed_time_width_discretization, None, None)\n\n        for arg in (\"\", \"5.3.1\", \"5.3\", \"abc\", \"-5\", \"0\"):\n            self.assertIsInstance(ftw(arg, 1), str)\n\n        with patch(\"Orange.preprocess.discretize.FixedTimeWidth\") as disc:\n            self.assertNotIsInstance(ftw(\"5\", 2), str)\n            disc.assert_called_with(5, 2)\n\n            self.assertNotIsInstance(ftw(\"5\", 3), str)\n            disc.assert_called_with(35, 2)\n\n            self.assertNotIsInstance(ftw(\"5\", 4), str)\n            disc.assert_called_with(5, 3)\n\n        with patch(\"Orange.preprocess.discretize.FixedTimeWidth\",\n                   side_effect=TooManyIntervals):\n            self.assertIsInstance(ftw(\"42\", 3), str)\n\n    def test_custom_discretization(self):\n        cd = partial(_custom_discretization, None, None)\n\n        for arg in (\"\", \"4 5\", \"2, 1, 5\", \"1, foo, 13\"):\n            self.assertIsInstance(cd(arg), str)\n\n        with patch(\"Orange.preprocess.discretize.Discretizer.\"\n                   \"create_discretized_var\") as disc:\n            cd(\"1, 1.25, 1.5, 4\")\n            disc.assert_called_with(None, [1, 1.25, 1.5, 4])\n\n    def test_mdl_discretization(self):\n        mdl = _mdl_discretization\n        data = Table(\"iris\")[::10]\n        var = data.domain[0]\n        with patch(\"Orange.preprocess.discretize.EntropyMDL\") as mdldisc:\n            mdl(data, var)\n            mdldisc.return_value.assert_called_with(data, var)\n            mdldisc.reset_mock()\n\n            data = data[:, :4]\n            self.assertIsInstance(mdl(data, var), str)\n            mdldisc.assert_not_called()\n\n            data = data.transform(Domain(data.domain[:3], data.domain[3]))\n            self.assertIsInstance(mdl(data, var), str)\n            mdldisc.assert_not_called()\n\n    def test_var_key(self):\n        self.assertEqual(variable_key(ContinuousVariable(\"foo\")),\n                         (\"foo\", False))\n        self.assertEqual(variable_key(TimeVariable(\"bar\")),\n                         (\"bar\", True))\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Orange installation attempts...\nAttempting installation with Python 3.7...\nrunning build_ext\nCompiling Orange/classification/_tree_scorers.pyx because it changed.\nCompiling Orange/data/_contingency.pyx because it changed.\nCompiling Orange/data/_io.pyx because it changed.\nCompiling Orange/data/_valuecount.pyx because it changed.\nCompiling Orange/data/_variable.pyx because it changed.\nCompiling Orange/distance/_distance.pyx because it changed.\nCompiling Orange/preprocess/_discretize.pyx because it changed.\nCompiling Orange/preprocess/_relieff.pyx because it changed.\nCompiling Orange/projection/_som.pyx because it changed.\n[1/9] Cythonizing Orange/classification/_tree_scorers.pyx\n[2/9] Cythonizing Orange/data/_contingency.pyx\n[3/9] Cythonizing Orange/data/_io.pyx\n[4/9] Cythonizing Orange/data/_valuecount.pyx\n[5/9] Cythonizing Orange/data/_variable.pyx\n[6/9] Cythonizing Orange/distance/_distance.pyx\n[7/9] Cythonizing Orange/preprocess/_discretize.pyx\n[8/9] Cythonizing Orange/preprocess/_relieff.pyx\n[9/9] Cythonizing Orange/projection/_som.pyx\nbuilding 'Orange.classification._tree_scorers' extension\ncreating build\ncreating build/temp.linux-x86_64-3.7\ncreating build/temp.linux-x86_64-3.7/Orange\ncreating build/temp.linux-x86_64-3.7/Orange/classification\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_tree_scorers.c -o build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o\ncreating build/lib.linux-x86_64-3.7\ncreating build/lib.linux-x86_64-3.7/Orange\ncreating build/lib.linux-x86_64-3.7/Orange/classification\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_tree_scorers.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._contingency' extension\ncreating build/temp.linux-x86_64-3.7/Orange/data\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_contingency.c -o build/temp.linux-x86_64-3.7/Orange/data/_contingency.o\ncreating build/lib.linux-x86_64-3.7/Orange/data\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_contingency.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._io' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_io.c -o build/temp.linux-x86_64-3.7/Orange/data/_io.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_io.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._valuecount' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_valuecount.c -o build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_valuecount.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.data._variable' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/data/_variable.c -o build/temp.linux-x86_64-3.7/Orange/data/_variable.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/data/_variable.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.distance._distance' extension\ncreating build/temp.linux-x86_64-3.7/Orange/distance\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/distance/_distance.c -o build/temp.linux-x86_64-3.7/Orange/distance/_distance.o\ncreating build/lib.linux-x86_64-3.7/Orange/distance\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/distance/_distance.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._discretize' extension\ncreating build/temp.linux-x86_64-3.7/Orange/preprocess\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_discretize.c -o build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o\ncreating build/lib.linux-x86_64-3.7/Orange/preprocess\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_discretize.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.preprocess._relieff' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/preprocess/_relieff.cpp -o build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/preprocess/_relieff.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.projection._som' extension\ncreating build/temp.linux-x86_64-3.7/Orange/projection\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/projection/_som.c -o build/temp.linux-x86_64-3.7/Orange/projection/_som.o\ncreating build/lib.linux-x86_64-3.7/Orange/projection\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/projection/_som.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.classification._simple_tree' extension\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/classification/_simple_tree.c -o build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o\ncc -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/classification/_simple_tree.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so\nbuilding 'Orange.widgets.utils._grid_density' extension\ncreating build/temp.linux-x86_64-3.7/Orange/widgets\ncreating build/temp.linux-x86_64-3.7/Orange/widgets/utils\ncc -pthread -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -I/tools/deps/include -I/tools/deps/include/ncursesw -fPIC -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include -I/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/include -I/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/include/python3.7m -c Orange/widgets/utils/_grid_density.cpp -o build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o\ncreating build/lib.linux-x86_64-3.7/Orange/widgets\ncreating build/lib.linux-x86_64-3.7/Orange/widgets/utils\nc++ -pthread -shared -L/tools/deps/lib build/temp.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.o -L/home/gcpuser/.local/share/uv/python/cpython-3.7.9-linux-x86_64-gnu/lib -lm -lpython3.7m -o build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nrunning develop\nrunning egg_info\ncreating Orange3.egg-info\nwriting Orange3.egg-info/PKG-INFO\nwriting dependency_links to Orange3.egg-info/dependency_links.txt\nwriting entry points to Orange3.egg-info/entry_points.txt\nwriting requirements to Orange3.egg-info/requires.txt\nwriting top-level names to Orange3.egg-info/top_level.txt\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest file 'Orange3.egg-info/SOURCES.txt'\nreading manifest template 'MANIFEST.in'\nadding license file 'LICENSE'\nwriting manifest file 'Orange3.egg-info/SOURCES.txt'\nrunning build_ext\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_tree_scorers.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/data/_contingency.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_io.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_valuecount.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/data/_variable.cpython-37m-x86_64-linux-gnu.so -> Orange/data\ncopying build/lib.linux-x86_64-3.7/Orange/distance/_distance.cpython-37m-x86_64-linux-gnu.so -> Orange/distance\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_discretize.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/preprocess/_relieff.cpython-37m-x86_64-linux-gnu.so -> Orange/preprocess\ncopying build/lib.linux-x86_64-3.7/Orange/projection/_som.cpython-37m-x86_64-linux-gnu.so -> Orange/projection\ncopying build/lib.linux-x86_64-3.7/Orange/classification/_simple_tree.cpython-37m-x86_64-linux-gnu.so -> Orange/classification\ncopying build/lib.linux-x86_64-3.7/Orange/widgets/utils/_grid_density.cpython-37m-x86_64-linux-gnu.so -> Orange/widgets/utils\nCreating /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/Orange3.egg-link (link to .)\nAdding Orange3 3.33.0.dev0+86b10c0 to easy-install.pth file\nInstalling orange-canvas script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nInstalled /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c\nProcessing dependencies for Orange3==3.33.0.dev0+86b10c0\nSearching for XlsxWriter==3.2.0\nBest match: XlsxWriter 3.2.0\nAdding XlsxWriter 3.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for xlrd==2.0.1\nBest match: xlrd 2.0.1\nAdding xlrd 2.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for setuptools==59.8.0\nBest match: setuptools 59.8.0\nAdding setuptools 59.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for serverfiles==0.3.1\nBest match: serverfiles 0.3.1\nAdding serverfiles 0.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for scipy==1.7.3\nBest match: scipy 1.7.3\nAdding scipy 1.7.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for scikit-learn==1.0.2\nBest match: scikit-learn 1.0.2\nAdding scikit-learn 1.0.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for requests==2.31.0\nBest match: requests 2.31.0\nAdding requests 2.31.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for qtconsole==5.4.4\nBest match: qtconsole 5.4.4\nAdding qtconsole 5.4.4 to easy-install.pth file\nInstalling jupyter-qtconsole script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for PyYAML==6.0.1\nBest match: PyYAML 6.0.1\nAdding PyYAML 6.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for python-louvain==0.16\nBest match: python-louvain 0.16\nAdding python-louvain 0.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pyqtgraph==0.12.3\nBest match: pyqtgraph 0.12.3\nAdding pyqtgraph 0.12.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pygments==2.17.2\nBest match: pygments 2.17.2\nAdding pygments 2.17.2 to easy-install.pth file\nInstalling pygmentize script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pip==24.0\nBest match: pip 24.0\nAdding pip 24.0 to easy-install.pth file\nInstalling pip script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling pip3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling pip3.10 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pandas==1.3.5\nBest match: pandas 1.3.5\nAdding pandas 1.3.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for orange-widget-base==4.22.0\nBest match: orange-widget-base 4.22.0\nAdding orange-widget-base 4.22.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for orange-canvas-core==0.1.35\nBest match: orange-canvas-core 0.1.35\nAdding orange-canvas-core 0.1.35 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for openpyxl==3.1.3\nBest match: openpyxl 3.1.3\nAdding openpyxl 3.1.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for openTSNE==1.0.1\nBest match: openTSNE 1.0.1\nAdding openTSNE 1.0.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for numpy==1.17.5\nBest match: numpy 1.17.5\nAdding numpy 1.17.5 to easy-install.pth file\nInstalling f2py script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling f2py3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling f2py3.7 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for networkx==2.6.3\nBest match: networkx 2.6.3\nAdding networkx 2.6.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for matplotlib==3.5.3\nBest match: matplotlib 3.5.3\nAdding matplotlib 3.5.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for keyrings.alt==4.2.0\nBest match: keyrings.alt 4.2.0\nAdding keyrings.alt 4.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for keyring==24.1.1\nBest match: keyring 24.1.1\nAdding keyring 24.1.1 to easy-install.pth file\nInstalling keyring script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for joblib==1.3.2\nBest match: joblib 1.3.2\nAdding joblib 1.3.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for httpx==0.24.1\nBest match: httpx 0.24.1\nAdding httpx 0.24.1 to easy-install.pth file\nInstalling httpx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for chardet==5.2.0\nBest match: chardet 5.2.0\nAdding chardet 5.2.0 to easy-install.pth file\nInstalling chardetect script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for Bottleneck==1.4.0\nBest match: Bottleneck 1.4.0\nAdding Bottleneck 1.4.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for baycomp==1.0.3\nBest match: baycomp 1.0.3\nAdding baycomp 1.0.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine==5.15.6\nBest match: PyQtWebEngine 5.15.6\nAdding PyQtWebEngine 5.15.6 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for PyQt5==5.15.10\nBest match: PyQt5 5.15.10\nAdding PyQt5 5.15.10 to easy-install.pth file\nInstalling pylupdate5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling pyrcc5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling pyuic5 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for AnyQt==0.2.0\nBest match: AnyQt 0.2.0\nAdding AnyQt 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for threadpoolctl==3.1.0\nBest match: threadpoolctl 3.1.0\nAdding threadpoolctl 3.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for urllib3==2.0.7\nBest match: urllib3 2.0.7\nAdding urllib3 2.0.7 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for certifi==2024.12.14\nBest match: certifi 2024.12.14\nAdding certifi 2024.12.14 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for charset-normalizer==3.4.1\nBest match: charset-normalizer 3.4.1\nAdding charset-normalizer 3.4.1 to easy-install.pth file\nInstalling normalizer script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for idna==3.10\nBest match: idna 3.10\nAdding idna 3.10 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for traitlets==5.9.0\nBest match: traitlets 5.9.0\nAdding traitlets 5.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for jupyter-client==7.4.9\nBest match: jupyter-client 7.4.9\nAdding jupyter-client 7.4.9 to easy-install.pth file\nInstalling jupyter-kernel script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling jupyter-kernelspec script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling jupyter-run script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pyzmq==26.2.0\nBest match: pyzmq 26.2.0\nAdding pyzmq 26.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for ipykernel==6.16.2\nBest match: ipykernel 6.16.2\nAdding ipykernel 6.16.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for jupyter-core==4.12.0\nBest match: jupyter-core 4.12.0\nAdding jupyter-core 4.12.0 to easy-install.pth file\nInstalling jupyter script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling jupyter-migrate script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling jupyter-troubleshoot script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for packaging==24.0\nBest match: packaging 24.0\nAdding packaging 24.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for QtPy==2.4.2\nBest match: QtPy 2.4.2\nAdding QtPy 2.4.2 to easy-install.pth file\nInstalling qtpy script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for ipython-genutils==0.2.0\nBest match: ipython-genutils 0.2.0\nAdding ipython-genutils 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for python-dateutil==2.9.0.post0\nBest match: python-dateutil 2.9.0.post0\nAdding python-dateutil 2.9.0.post0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pytz==2024.2\nBest match: pytz 2024.2\nAdding pytz 2024.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for typing-extensions==4.7.1\nBest match: typing-extensions 4.7.1\nAdding typing-extensions 4.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for requests-cache==1.1.1\nBest match: requests-cache 1.1.1\nAdding requests-cache 1.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for qasync==0.25.0\nBest match: qasync 0.25.0\nAdding qasync 0.25.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for commonmark==0.9.1\nBest match: commonmark 0.9.1\nAdding commonmark 0.9.1 to easy-install.pth file\nInstalling cmark script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for docutils==0.20.1\nBest match: docutils 0.20.1\nAdding docutils 0.20.1 to easy-install.pth file\nInstalling docutils script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for importlib-metadata==6.7.0\nBest match: importlib-metadata 6.7.0\nAdding importlib-metadata 6.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for dictdiffer==0.9.0\nBest match: dictdiffer 0.9.0\nAdding dictdiffer 0.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for et-xmlfile==1.1.0\nBest match: et-xmlfile 1.1.0\nAdding et-xmlfile 1.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pyparsing==3.1.4\nBest match: pyparsing 3.1.4\nAdding pyparsing 3.1.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for fonttools==4.38.0\nBest match: fonttools 4.38.0\nAdding fonttools 4.38.0 to easy-install.pth file\nInstalling fonttools script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling pyftmerge script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling pyftsubset script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling ttx script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for cycler==0.11.0\nBest match: cycler 0.11.0\nAdding cycler 0.11.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for Pillow==9.5.0\nBest match: Pillow 9.5.0\nAdding Pillow 9.5.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for kiwisolver==1.4.5\nBest match: kiwisolver 1.4.5\nAdding kiwisolver 1.4.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for jaraco.classes==3.2.3\nBest match: jaraco.classes 3.2.3\nAdding jaraco.classes 3.2.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for SecretStorage==3.3.3\nBest match: SecretStorage 3.3.3\nAdding SecretStorage 3.3.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for importlib-resources==5.12.0\nBest match: importlib-resources 5.12.0\nAdding importlib-resources 5.12.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for jeepney==0.8.0\nBest match: jeepney 0.8.0\nAdding jeepney 0.8.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for httpcore==0.17.3\nBest match: httpcore 0.17.3\nAdding httpcore 0.17.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for sniffio==1.3.1\nBest match: sniffio 1.3.1\nAdding sniffio 1.3.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for PyQt5-sip==12.13.0\nBest match: PyQt5-sip 12.13.0\nAdding PyQt5-sip 12.13.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for PyQtWebEngine-Qt5==5.15.16\nBest match: PyQtWebEngine-Qt5 5.15.16\nAdding PyQtWebEngine-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for PyQt5-Qt5==5.15.16\nBest match: PyQt5-Qt5 5.15.16\nAdding PyQt5-Qt5 5.15.16 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for nest-asyncio==1.6.0\nBest match: nest-asyncio 1.6.0\nAdding nest-asyncio 1.6.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for tornado==6.2\nBest match: tornado 6.2\nAdding tornado 6.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for entrypoints==0.4\nBest match: entrypoints 0.4\nAdding entrypoints 0.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for ipython==7.34.0\nBest match: ipython 7.34.0\nAdding ipython 7.34.0 to easy-install.pth file\nInstalling iptest script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling iptest3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling ipython script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\nInstalling ipython3 script to /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/bin\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for psutil==6.1.1\nBest match: psutil 6.1.1\nAdding psutil 6.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for matplotlib-inline==0.1.6\nBest match: matplotlib-inline 0.1.6\nAdding matplotlib-inline 0.1.6 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for debugpy==1.7.0\nBest match: debugpy 1.7.0\nAdding debugpy 1.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for six==1.17.0\nBest match: six 1.17.0\nAdding six 1.17.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for cattrs==23.1.2\nBest match: cattrs 23.1.2\nAdding cattrs 23.1.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for attrs==24.2.0\nBest match: attrs 24.2.0\nAdding attrs 24.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for platformdirs==4.0.0\nBest match: platformdirs 4.0.0\nAdding platformdirs 4.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for url-normalize==1.4.3\nBest match: url-normalize 1.4.3\nAdding url-normalize 1.4.3 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for zipp==3.15.0\nBest match: zipp 3.15.0\nAdding zipp 3.15.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for more-itertools==9.1.0\nBest match: more-itertools 9.1.0\nAdding more-itertools 9.1.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for cryptography==44.0.0\nBest match: cryptography 44.0.0\nAdding cryptography 44.0.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for h11==0.14.0\nBest match: h11 0.14.0\nAdding h11 0.14.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for anyio==3.7.1\nBest match: anyio 3.7.1\nAdding anyio 3.7.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for jedi==0.19.2\nBest match: jedi 0.19.2\nAdding jedi 0.19.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for backcall==0.2.0\nBest match: backcall 0.2.0\nAdding backcall 0.2.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for prompt-toolkit==3.0.48\nBest match: prompt-toolkit 3.0.48\nAdding prompt-toolkit 3.0.48 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pexpect==4.9.0\nBest match: pexpect 4.9.0\nAdding pexpect 4.9.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for decorator==5.1.1\nBest match: decorator 5.1.1\nAdding decorator 5.1.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pickleshare==0.7.5\nBest match: pickleshare 0.7.5\nAdding pickleshare 0.7.5 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for exceptiongroup==1.2.2\nBest match: exceptiongroup 1.2.2\nAdding exceptiongroup 1.2.2 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for cffi==1.15.1\nBest match: cffi 1.15.1\nAdding cffi 1.15.1 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for parso==0.8.4\nBest match: parso 0.8.4\nAdding parso 0.8.4 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for wcwidth==0.2.13\nBest match: wcwidth 0.2.13\nAdding wcwidth 0.2.13 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for ptyprocess==0.7.0\nBest match: ptyprocess 0.7.0\nAdding ptyprocess 0.7.0 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nSearching for pycparser==2.21\nBest match: pycparser 2.21\nAdding pycparser 2.21 to easy-install.pth file\n\nUsing /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages\nFinished processing dependencies for Orange3==3.33.0.dev0+86b10c0\nVerifying Orange installation...\n✅ Orange installation successful!\nSuccessfully installed orange using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 18 packages in 130ms\nPrepared 18 packages in 62ms\nInstalled 18 packages in 711ms\n + cython==0.29.37\n + exceptiongroup==1.2.2\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + numpy==1.17.5\n + packaging==24.0\n + pluggy==1.2.0\n + pyqt5==5.15.10\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.13.0\n + pyqtwebengine==5.15.6\n + pyqtwebengine-qt5==5.15.16\n + pytest==7.4.4\n + setuptools==59.8.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + wheel==0.42.0\n + zipp==3.15.0\nResolved 54 packages in 274ms\nInstalled 47 packages in 587ms\n + anyio==3.7.1\n + baycomp==1.0.3\n + bottleneck==1.4.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + chardet==5.2.0\n + charset-normalizer==3.4.1\n + cryptography==44.0.0\n + cycler==0.11.0\n + et-xmlfile==1.1.0\n + fonttools==4.38.0\n + h11==0.14.0\n + httpcore==0.17.3\n + httpx==0.24.1\n + idna==3.10\n + importlib-resources==5.12.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + joblib==1.3.2\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + kiwisolver==1.4.5\n + matplotlib==3.5.3\n + more-itertools==9.1.0\n + networkx==2.6.3\n + openpyxl==3.1.3\n + opentsne==1.0.1\n + pandas==1.3.5\n + pillow==9.5.0\n + pip==24.0\n + pycparser==2.21\n + pyparsing==3.1.4\n + python-dateutil==2.9.0.post0\n + python-louvain==0.16\n + pytz==2024.2\n + pyyaml==6.0.1\n + requests==2.31.0\n + scikit-learn==1.0.2\n + scipy==1.7.3\n + secretstorage==3.3.3\n + serverfiles==0.3.1\n + six==1.17.0\n + sniffio==1.3.1\n + threadpoolctl==3.1.0\n + urllib3==2.0.7\n + xlrd==2.0.1\n + xlsxwriter==3.2.0\nResolved 59 packages in 267ms\nInstalled 38 packages in 212ms\n + anyqt==0.2.0\n + attrs==24.2.0\n + backcall==0.2.0\n + cattrs==23.1.2\n + commonmark==0.9.1\n + debugpy==1.7.0\n + decorator==5.1.1\n + dictdiffer==0.9.0\n + docutils==0.20.1\n + entrypoints==0.4\n + ipykernel==6.16.2\n + ipython==7.34.0\n + ipython-genutils==0.2.0\n + jedi==0.19.2\n + jupyter-client==7.4.9\n + jupyter-core==4.12.0\n + matplotlib-inline==0.1.6\n + nest-asyncio==1.6.0\n + orange-canvas-core==0.1.35\n + orange-widget-base==4.22.0\n + parso==0.8.4\n + pexpect==4.9.0\n + pickleshare==0.7.5\n + platformdirs==4.0.0\n + prompt-toolkit==3.0.48\n + psutil==6.1.1\n + ptyprocess==0.7.0\n + pygments==2.17.2\n + pyqtgraph==0.12.3\n + pyzmq==26.2.0\n + qasync==0.25.0\n + qtconsole==5.4.4\n + qtpy==2.4.2\n + requests-cache==1.1.1\n + tornado==6.2\n + traitlets==5.9.0\n + url-normalize==1.4.3\n + wcwidth==0.2.13\nResolved 2 packages in 13ms\nInstalled 2 packages in 17ms\n + psycopg2==2.9.9\n + pymssql==2.3.2\nResolved 19 packages in 98ms\nInstalled 5 packages in 1.10s\n + catboost==1.2.7\n + graphviz==0.20.1\n + plotly==5.18.0\n + tenacity==8.2.3\n + xgboost==1.6.2\nAudited 2 packages in 3ms\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/classification/_tree_scorers.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_2find_threshold_entropy’:\nOrange/classification/_tree_scorers.c:3427:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 3427 |   __pyx_t_7 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_6find_threshold_MSE’:\nOrange/classification/_tree_scorers.c:4883:27: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 4883 |   __pyx_t_1 = ((__pyx_v_N <= __pyx_v_min_leaf) != 0);\n      |                           ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_8find_binarization_MSE’:\nOrange/classification/_tree_scorers.c:5629:43: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5629 |           for (__pyx_t_19 = 0; __pyx_t_19 < __pyx_t_18; __pyx_t_19+=1) {\n      |                                           ^\nOrange/classification/_tree_scorers.c:5750:39: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5750 |           __pyx_t_20 = ((__pyx_v_left >= __pyx_v_min_leaf) != 0);\n      |                                       ^~\nOrange/classification/_tree_scorers.c:5756:53: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 5756 |           __pyx_t_20 = (((__pyx_v_N - __pyx_v_left) >= __pyx_v_min_leaf) != 0);\n      |                                                     ^~\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_14compute_predictions_csr’:\nOrange/classification/_tree_scorers.c:7217:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7217 |         for (__pyx_t_12 = 0; __pyx_t_12 < __pyx_t_11; __pyx_t_12+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c: In function ‘__pyx_pf_6Orange_14classification_13_tree_scorers_16compute_predictions_csc’:\nOrange/classification/_tree_scorers.c:7874:41: warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’ [-Wsign-compare]\n 7874 |         for (__pyx_t_12 = 0; __pyx_t_12 < __pyx_t_11; __pyx_t_12+=1) {\n      |                                         ^\nOrange/classification/_tree_scorers.c:7934:150: warning: comparison of integer expressions of different signedness: ‘__pyx_t_5numpy_int32_t’ {aka ‘int’} and ‘unsigned int’ [-Wsign-compare]\n 7934 |               __pyx_t_16 = (((*((__pyx_t_5numpy_int32_t const  *) ( /* dim=0 */ (__pyx_v_indices.data + __pyx_t_15 * __pyx_v_indices.strides[0]) ))) != __pyx_v_i) != 0);\n      |                                                                                                                                                      ^~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_contingency.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/data/_io.c: In function ‘__pyx_pf_6Orange_4data_3_io_sparse_read_float.constprop’:\nOrange/data/_io.c:4120:43: warning: ‘__pyx_v_decs’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4120 |           __pyx_v_value = ((__pyx_v_value * __pyx_v_decs) + (__pyx_v_c & 0xf));\n      |                            ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/data/_io.c:4318:24: warning: ‘__pyx_v_value’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4318 |           __pyx_t_10 = PyFloat_FromDouble(__pyx_v_value); if (unlikely(!__pyx_t_10)) __PYX_ERR(0, 223, __pyx_L5_error)\n      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:683:40: warning: ‘__pyx_v_attr_index’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n  683 |   #define PyInt_FromLong               PyLong_FromLong\n      |                                        ^~~~~~~~~~~~~~~\nOrange/data/_io.c:1590:7: note: ‘__pyx_v_attr_index’ was declared here\n 1590 |   int __pyx_v_attr_index;\n      |       ^~~~~~~~~~~~~~~~~~\nOrange/data/_io.c:3183:25: warning: ‘__pyx_v_atomp’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3183 |           __pyx_v_atomp = (__pyx_v_atomp + 1);\n      |           ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/data/_valuecount.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/distance/_distance.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_discretize.c:774:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/preprocess/_relieff.cpp:790:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nIn file included from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarraytypes.h:1830,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/ndarrayobject.h:12,\n                 from /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/arrayobject.h:4,\n                 from Orange/projection/_som.c:775:\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/numpy/core/include/numpy/npy_1_7_deprecated_api.h:17:2: warning: #warning \"Using deprecated NumPy API, disable it with \" \"#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION\" [-Wcpp]\n   17 | #warning \"Using deprecated NumPy API, disable it with \" \\\n      |  ^~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_5update_hex’:\nOrange/projection/_som.c:3863:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3863 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3862:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3862 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_8update_sparse.constprop’:\nOrange/projection/_som.c:4934:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4934 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4933:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4933 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_7get_winners_sparse’:\nOrange/projection/_som.c:4732:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4732 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4307:7: note: ‘__pyx_v_win_y’ was declared here\n 4307 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:4721:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 4721 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_27, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_26, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:4306:7: note: ‘__pyx_v_win_x’ was declared here\n 4306 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pf_6Orange_10projection_4_som_10update_sparse_hex.constprop’:\nOrange/projection/_som.c:5682:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5682 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:5681:7: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 5681 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_1get_winners’:\nOrange/projection/_som.c:3184:223: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3184 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_y;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2887:7: note: ‘__pyx_v_win_y’ was declared here\n 2887 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c:3173:223: warning: ‘__pyx_v_win_x’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3173 |           *__Pyx_BufPtrStrided2d(__pyx_t_5numpy_int16_t *, __pyx_pybuffernd_winners.rcbuffer->pybuffer.buf, __pyx_t_23, __pyx_pybuffernd_winners.diminfo[0].strides, __pyx_t_22, __pyx_pybuffernd_winners.diminfo[1].strides) = __pyx_v_win_x;\n      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~\nOrange/projection/_som.c:2886:7: note: ‘__pyx_v_win_x’ was declared here\n 2886 |   int __pyx_v_win_x;\n      |       ^~~~~~~~~~~~~\nOrange/projection/_som.c: In function ‘__pyx_pw_6Orange_10projection_4_som_3update’:\nOrange/projection/_som.c:3357:7: warning: ‘__pyx_v_win_y’ may be used uninitialized in this function [-Wmaybe-uninitialized]\n 3357 |   int __pyx_v_win_y;\n      |       ^~~~~~~~~~~~~\nOrange/classification/_simple_tree.c: In function ‘gain_ratio_c’:\nOrange/classification/_simple_tree.c:63:9: warning: implicit declaration of function ‘qsort_r’; did you mean ‘qsort’? [-Wimplicit-function-declaration]\n   63 |         qsort_r(base, nel, size, compar, thunk)\n      |         ^~~~~~~\nOrange/classification/_simple_tree.c:147:9: note: in expansion of macro ‘QSORT_R_FUNC’\n  147 |         QSORT_R_FUNC(examples, size, sizeof(struct Example), (void*) &compar_attr, compar_examples);\n      |         ^~~~~~~~~~~~\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/setuptools/command/easy_install.py:159: EasyInstallDeprecationWarning: easy_install command is deprecated. Use build and pip and other standards-based tools.\n  EasyInstallDeprecationWarning,\n/home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c/.venv/lib/python3.7/site-packages/setuptools/command/install.py:37: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.\n  setuptools.SetuptoolsDeprecationWarning,\nwarning: no files found matching 'README*' under directory 'Orange'\nwarning: no files found matching 'LICENSE' under directory 'Orange'\nwarning: no files found matching '*.basket' under directory 'Orange/datasets'\nwarning: no files found matching '*.svg' under directory 'Orange/canvas'\nwarning: no files found matching '*.js' under directory 'Orange/widgets'\nwarning: no files found matching '*.css' under directory 'Orange/widgets'\nwarning: no files found matching '*.html' under directory 'Orange/widgets'\nwarning: no files found matching '*.svg' under directory 'distribute'\nwarning: no files found matching 'COPYING'\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c\nplugins: anyio-3.7.1\ncollected 34 items\n\nr2e_tests/test_1.py sss...............................                   [100%]\n\n==================================== PASSES ====================================\n___________________________ TestUtils.test_show_tip ____________________________\n----------------------------- Captured stderr call -----------------------------\nThis plugin does not support raise()\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_all\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_change_selection_update_interface\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_copy_to_manual\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_discretize_var\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_empty_data\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_get_values\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_image_export\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_migration_2_3\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_report\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_set_values\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_update_discretizations\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_update_hints\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_varkeys_for_selection\nPASSED r2e_tests/test_1.py::TestValidator::test_validate\nPASSED r2e_tests/test_1.py::TestModels::test_delegate\nPASSED r2e_tests/test_1.py::TestModels::test_image_export\nPASSED r2e_tests/test_1.py::TestModels::test_layout\nPASSED r2e_tests/test_1.py::TestModels::test_minimum_size\nPASSED r2e_tests/test_1.py::TestModels::test_model\nPASSED r2e_tests/test_1.py::TestModels::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestDefaultDiscModel::test_counts\nPASSED r2e_tests/test_1.py::TestDefaultDiscModel::test_data\nPASSED r2e_tests/test_1.py::TestUtils::test_custom_discretization\nPASSED r2e_tests/test_1.py::TestUtils::test_fixed_time_width_disc\nPASSED r2e_tests/test_1.py::TestUtils::test_fixed_width_disc\nPASSED r2e_tests/test_1.py::TestUtils::test_format_desc\nPASSED r2e_tests/test_1.py::TestUtils::test_mdl_discretization\nPASSED r2e_tests/test_1.py::TestUtils::test_show_tip\nPASSED r2e_tests/test_1.py::TestUtils::test_var_key\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\n======================== 31 passed, 3 skipped in 5.55s =========================\nWidgets left: 0    Max widgets: 1309 \n\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/orange3_86b10c09c04fb66f04454f98826eb67de5e3117c\nplugins: anyio-3.7.1\ncollected 34 items\n\nr2e_tests/test_1.py sss.F.............................                   [100%]\n\n=================================== FAILURES ===================================\n___________ TestOWDiscretize.test_change_selection_update_interface ____________\n\nself = <r2e_tests.test_1.TestOWDiscretize testMethod=test_change_selection_update_interface>\n\n    def test_change_selection_update_interface(self):\n        w = self.widget\n        self.send_signal(w.Inputs.data, self.data)\n        w.var_hints = {\n            DefaultKey: DefaultHint,\n            (\"x\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"y\", False): VarHint(Methods.FixedWidth, (\"10\", )),\n            (\"z\", False): VarHint(Methods.FixedWidth, (\"5\", )),\n            (\"t\", False): VarHint(Methods.Binning, (5, ))\n        }\n    \n        select_rows(w.varview, (0, 1))\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n        self.assertEqual(w.width_line.text(), \"10\")\n    \n        select_rows(w.varview, (1, 2))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertTrue(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.Custom).isEnabled())\n        self.assertTrue(w.copy_to_custom.isEnabled())\n    \n        select_rows(w.varview, (2, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n    \n        select_rows(w.varview, (3, 4))\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isChecked())\n        self.assertFalse(w.button_group.button(Methods.FixedWidth).isEnabled())\n        self.assertTrue(w.button_group.button(Methods.FixedWidthTime).isEnabled())\n        self.assertFalse(w.button_group.button(Methods.Custom).isEnabled())\n>       self.assertFalse(w.copy_to_custom.isEnabled())\nE       AssertionError: True is not false\n\nr2e_tests/test_1.py:222: AssertionError\n==================================== PASSES ====================================\n___________________________ TestUtils.test_show_tip ____________________________\n----------------------------- Captured stderr call -----------------------------\nThis plugin does not support raise()\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_all\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_copy_to_manual\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_discretize_var\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_empty_data\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_get_values\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_image_export\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_migration_2_3\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_minimum_size\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_report\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_set_values\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_update_discretizations\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_update_hints\nPASSED r2e_tests/test_1.py::TestOWDiscretize::test_varkeys_for_selection\nPASSED r2e_tests/test_1.py::TestValidator::test_validate\nPASSED r2e_tests/test_1.py::TestModels::test_delegate\nPASSED r2e_tests/test_1.py::TestModels::test_image_export\nPASSED r2e_tests/test_1.py::TestModels::test_layout\nPASSED r2e_tests/test_1.py::TestModels::test_minimum_size\nPASSED r2e_tests/test_1.py::TestModels::test_model\nPASSED r2e_tests/test_1.py::TestModels::test_msg_base_class\nPASSED r2e_tests/test_1.py::TestDefaultDiscModel::test_counts\nPASSED r2e_tests/test_1.py::TestDefaultDiscModel::test_data\nPASSED r2e_tests/test_1.py::TestUtils::test_custom_discretization\nPASSED r2e_tests/test_1.py::TestUtils::test_fixed_time_width_disc\nPASSED r2e_tests/test_1.py::TestUtils::test_fixed_width_disc\nPASSED r2e_tests/test_1.py::TestUtils::test_format_desc\nPASSED r2e_tests/test_1.py::TestUtils::test_mdl_discretization\nPASSED r2e_tests/test_1.py::TestUtils::test_show_tip\nPASSED r2e_tests/test_1.py::TestUtils::test_var_key\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:244: image exporting not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:238: minimum size not tested as .widget was not set\nSKIPPED [1] .venv/lib/python3.7/site-packages/orangewidget/tests/base.py:250: msg base class not tested as .widget was not set\nFAILED r2e_tests/test_1.py::TestOWDiscretize::test_change_selection_update_interface\n=================== 1 failed, 30 passed, 3 skipped in 4.14s ====================\nWidgets left: 0    Max widgets: 1309 \n\n",
    "old_commit_res_stderr": ""
}
Orange/widgets/data/owdiscretize.py Orange/widgets/data/tests/test_owdiscretize.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `copy_to_custom` Button Remains Enabled When Default Discretization is Selected

**Description:**
In the OWDiscretize widget, selecting the default discretization method should disable the `copy_to_custom` button to prevent users from copying settings that are not applicable. However, the button remains enabled even when the default method is chosen, leading to unexpected behavior.

**Example Scenario:**

When multiple variables are selected and the default discretization method is applied, the `copy_to_custom` button should be disabled. Instead, it stays enabled, which can confuse users and allow inappropriate actions.

```python
w = OWDiscretize()
w.var_hints = {
    DefaultKey: DefaultHint,
    ("x", False): VarHint(Methods.FixedWidth, ("10",)),
    ("y", False): VarHint(Methods.FixedWidth, ("10",)),
    ("z", False): VarHint(Methods.FixedWidth, ("5",)),
    ("t", False): VarHint(Methods.Binning, (5, ))
}

select_rows(w.varview, (3, 4))
# Expected: copy_to_custom button should be disabled
# Actual: copy_to_custom button is still enabled
```

**Expected Behavior:**
The `copy_to_custom` button should be disabled when the default discretization method is selected to ensure the interface behaves correctly and prevents unintended actions.

**Actual Behavior:**
The `copy_to_custom` button remains enabled even when the default discretization method is selected, causing inconsistent UI behavior and potential misuse.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/data/owdiscretize.py
import re
import html
from enum import IntEnum
from typing import Optional, Tuple, Union, Callable, NamedTuple, Dict, List

from AnyQt.QtCore import (
    Qt, QTimer, QPoint, QItemSelectionModel, QSize, QAbstractListModel)
from AnyQt.QtGui import (
    QValidator, QPalette, QDoubleValidator, QIntValidator,  QColor)
from AnyQt.QtWidgets import (
    QListView, QHBoxLayout, QStyledItemDelegate, QButtonGroup, QWidget,
    QLineEdit, QToolTip, QLabel, QApplication,
    QSpinBox, QSizePolicy, QRadioButton, QComboBox)

from orangewidget.settings import Setting
from orangewidget.utils import listview

from Orange.data import (
    Variable, ContinuousVariable, DiscreteVariable, TimeVariable, Domain, Table)
import Orange.preprocess.discretize as disc
from Orange.widgets import widget, gui
from Orange.widgets.utils import unique_everseen
from Orange.widgets.utils.itemmodels import DomainModel
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output
from Orange.widgets.data.oweditdomain import FixedSizeButton


re_custom_sep = re.compile(r"\s*,\s*")
time_units = ["year", "month", "day", "week", "hour", "minute", "second"]
INVALID_WIDTH = "invalid width"
TOO_MANY_INTERVALS = "too many intervals"


def _fixed_width_discretization(
        data: Table,
        var: Union[ContinuousVariable, str, int],
        width: str) -> Union[DiscreteVariable, str]:
    """
    Discretize numeric variable with fixed bin width. Used in method definition.

    Width is given as string (coming from line edit). The labels for the new
    variable will have the same number of digits; this is more appropriate
    than the number of digits in the original variable, which may be too large.

    Args:
        data: data used to deduce the interval of values
        var: variable to discretize
        width: interval width

    Returns:
        Discrete variable, if successful; a string with error otherwise
    """
    digits = len(width) - width.index(".") - 1 if "." in width else 0
    try:
        width = float(width)
    except ValueError:
        return INVALID_WIDTH
    if width <= 0:
        return INVALID_WIDTH
    try:
        return disc.FixedWidth(width, digits)(data, var)
    except disc.TooManyIntervals:
        return TOO_MANY_INTERVALS


# pylint: disable=invalid-name
def _fixed_time_width_discretization(
        data: Table,
        var: Union[TimeVariable, str, int],
        width: str, unit: int) -> Union[DiscreteVariable]:
    """
    Discretize time variable with fixed bin width. Used in method definition.

    Width is given as string (coming from line edit).

    Args:
        data: data used to deduce the interval of values
        var: variable to discretize
        width: interval width
        unit: 0 = year, 1 = month, 2 = week, 3 = day, 4 = hour, 5 = min, 6 = sec

    Returns:
        Discrete variable, if successful; a string with error otherwise
    """
    try:
        width = int(width)
    except ValueError:
        return INVALID_WIDTH
    if width <= 0:
        return INVALID_WIDTH
    if unit == 3:  # week
        width *= 7
    unit -= unit >= 3
    try:
        return disc.FixedTimeWidth(width, unit)(data, var)
    except disc.TooManyIntervals:
        return TOO_MANY_INTERVALS


def _mdl_discretization(
        data: Table,
        var: Union[ContinuousVariable, str, int]) -> Union[DiscreteVariable, str]:
    if not data.domain.has_discrete_class:
        return "no discrete class"
    return disc.EntropyMDL()(data, var)


def _custom_discretization(
        _,
        var: Union[ContinuousVariable, str, int],
        points: str) -> Union[DiscreteVariable, str]:
    """
    Discretize variable using custom thresholds. Used in method definition.

    Thresholds are given as string (coming from line edit).

    Args:
        data: data used to deduce the interval of values
        var: variable to discretize
        points: thresholds

    Returns:
        Discrete variable, if successful; a string with error otherwise
    """
    try:
        cuts = [float(x) for x in re_custom_sep.split(points.strip())]
    except ValueError:
        cuts = []
    if any(x >= y for x, y in zip(cuts, cuts[1:])):
        cuts = []
    if not cuts:
        return "invalid cuts"
    return disc.Discretizer.create_discretized_var(var, cuts)


class Methods(IntEnum):
    # pylint: disable=invalid-name
    Default, Keep, MDL, EqualFreq, EqualWidth, Remove, Custom, Binning, \
        FixedWidth, FixedWidthTime = range(10)


class MethodDesc(NamedTuple):
    """
    Definitions of all methods; used for creation of interface and calling
    """
    id_: Methods  # Method id
    label: str  # Label used for radio button
    short_desc: str  # Short descriptions for list views
    tooltip: str  # Tooltip for radio button
    # Discretization function, see, e.g. fixed_width_discretization
    function: Optional[Callable[..., Union[DiscreteVariable, str]]]
    controls: Tuple[str, ...] = ()  # Widget attributes with related ux controls


Options: Dict[Methods, MethodDesc] = {
    method.id_: method
    for method in (
        MethodDesc(Methods.Default,
                   "Use default setting", "default",
                   "Treat the variable as defined in 'default setting'",
                   None,
                   ()),
        MethodDesc(Methods.Keep,
                   "Keep numeric", "keep",
                   "Keep the variable as is",
                   lambda data, var: var,
                   ()),
        MethodDesc(Methods.MDL,
                   "Entropy vs. MDL", "entropy",
                   "Split values until MDL exceeds the entropy (Fayyad-Irani)\n"
                   "(requires discrete class variable)",
                   _mdl_discretization,
                   ()),
        MethodDesc(Methods.EqualFreq,
                   "Equal frequency, intervals: ", "equal freq, k={}",
                   "Create bins with same number of instances",
                   lambda data, var, k: disc.EqualFreq(k)(data, var),
                   ("freq_spin", )),
        MethodDesc(Methods.EqualWidth,
                   "Equal width, intervals: ", "equal width, k={}",
                   "Create bins of the same width",
                   lambda data, var, k: disc.EqualWidth(k)(data, var),
                   ("width_spin", )),
        MethodDesc(Methods.Remove,
                   "Remove", "remove",
                   "Remove variable",
                   lambda *_: None,
                   ()),
        MethodDesc(Methods.Binning,
                   "Natural binning, desired bins: ", "binning, desired={}",
                   "Create bins with nice thresholds; "
                   "try matching desired number of bins",
                   lambda data, var, nbins: disc.Binning(nbins)(data, var),
                   ("binning_spin", )),
        MethodDesc(Methods.FixedWidth,
                   "Fixed width: ", "fixed width {}",
                   "Create bins with the given width (not for time variables)",
                   _fixed_width_discretization,
                   ("width_line", )),
        MethodDesc(Methods.FixedWidthTime,
                   "Time interval: ", "time interval, {} {}",
                   "Create bins with the give width (for time variables)",
                   _fixed_time_width_discretization,
                   ("width_time_line", "width_time_unit")),
        MethodDesc(Methods.Custom,
                   "Custom: ", "custom: {}",
                   "Use manually specified thresholds",
                   _custom_discretization,
                   ("threshold_line", ))
    )
}


class VarHint(NamedTuple):
    """Description for settings"""
    method_id: Methods
    args: Tuple[Union[str, float, int]]


class DiscDesc(NamedTuple):
    """Data for list view model"""
    hint: VarHint
    points: str
    values: Tuple[str]


KeyType = Optional[Tuple[str, bool]]
DefaultHint = VarHint(Methods.Keep, ())
DefaultKey = None


def variable_key(var: ContinuousVariable) -> KeyType:
    """Key for that variable in var_hints and discretized_vars"""
    return var.name, isinstance(var, TimeVariable)


class ListViewSearch(listview.ListViewSearch):
    """
    A list view with two components shown above it:
    - a listview containing a single item representing default settings
    - a filter for search

    The class is based on listview.ListViewSearch and needs to have the same
    name in order to override its private method __layout.

    Inherited __init__ calls __layout, so `default_view` must be constructed
    there. Construction before calling super().__init__ doesn't work because
    PyQt does not allow it.
    """
    class DiscDelegate(QStyledItemDelegate):
        """
        A delegate that shows items (variables) with specific settings in bold
        """
        def initStyleOption(self, option, index):
            super().initStyleOption(option, index)
            option.font.setBold(index.data(Qt.UserRole).hint is not None)

    def __init__(self, *args, **kwargs):
        self.default_view = None
        super().__init__(preferred_size=QSize(350, -1), *args, **kwargs)
        self.setItemDelegate(self.DiscDelegate(self))

    def select_default(self):
        """Select the item representing default settings"""
        index = self.default_view.model().index(0)
        self.default_view.selectionModel().select(
            index, QItemSelectionModel.Select)

    # pylint: disable=unused-private-member
    def __layout(self):
        if self.default_view is None:  # __layout was called from __init__
            view = self.default_view = QListView(self)
            view.setModel(DefaultDiscModel())
            view.verticalScrollBar().setDisabled(True)
            view.horizontalScrollBar().setDisabled(True)
            view.setHorizontalScrollBarPolicy(
                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
            view.setVerticalScrollBarPolicy(
                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
            font = view.font()
            font.setBold(True)
            view.setFont(font)
        else:
            view = self.default_view

        # Put the list view with default on top
        margins = self.viewportMargins()
        def_height = view.sizeHintForRow(0) + 2 * view.spacing() + 2
        view.setGeometry(0, 0, self.geometry().width(), def_height)
        view.setFixedHeight(def_height)

        # Then search
        search = self.__search
        src_height = search.sizeHint().height()
        size = self.size()
        search.setGeometry(0, def_height + 2, size.width(), src_height)

        # Then the real list view
        margins.setTop(def_height + 2 + src_height)
        self.setViewportMargins(margins)


def format_desc(hint: VarHint) -> str:
    """Describe the method and its parameters; used in list views and report"""
    if hint is None:
        return Options[Methods.Default].short_desc
    desc = Options[hint.method_id].short_desc
    if hint.method_id == Methods.FixedWidthTime:
        width, unit = hint.args
        unit = time_units[unit]
        try:
            width = int(width)
        except ValueError:
            unit += "(s)"
        else:
            if width != 1:
                unit += "s"
        return desc.format(width, unit)
    return desc.format(*hint.args)


class DiscDomainModel(DomainModel):
    """
    Domain model that adds description of discretization methods and thresholds

    Also provides a tooltip that shows bins, that is, labels of the discretized
    variable.
    """
    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.ToolTipRole:
            var = self[index.row()]
            data = index.data(Qt.UserRole)
            tip = f"<b>{var.name}: </b>"
            values = map(html.escape, data.values)
            if not data.values:
                return None
            if len(data.values) <= 3:
                return f'<p style="white-space:pre">{tip}' \
                       f'{",&nbsp;&nbsp;".join(values)}</p>'
            else:
                return tip + "<br/>" \
                    + "".join(f"- {value}<br/>" for value in values)
        value = super().data(index, role)
        if role == Qt.DisplayRole:
            hint, points, values = index.data(Qt.UserRole)
            value += f" ({format_desc(hint)}){points}"
        return value


class DefaultDiscModel(QAbstractListModel):
    """
    A model used for showing "Default settings" above the list view with var
    """
    icon = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if DefaultDiscModel.icon is None:
            DefaultDiscModel.icon = gui.createAttributePixmap(
                "★", QColor(0, 0, 0, 0), Qt.black)
        self.hint: VarHint = DefaultHint

    @staticmethod
    def rowCount(parent):
        return 0 if parent.isValid() else 1

    @staticmethod
    def columnCount(parent):
        return 0 if parent.isValid() else 1

    def data(self, _, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return "Default setting: " + format_desc(self.hint)
        elif role == Qt.DecorationRole:
            return DefaultDiscModel.icon
        elif role == Qt.ToolTipRole:
            return "Default setting for variables without specific setings"
        return None

    def setData(self, index, value, role=Qt.DisplayRole):
        if role == Qt.UserRole:
            self.hint = value
            self.dataChanged.emit(index, index)


class IncreasingNumbersListValidator(QValidator):
    """
    A validator for custom thresholds

    Requires a string with increasing comma-separated values. If the string
    ends with number followed by space, it inserts a comma.
    """
    @staticmethod
    def validate(string: str, pos: int) -> Tuple[QValidator.State, str, int]:
        for i, c in enumerate(string, start=1):
            if c not in "+-., 0123456789":
                return QValidator.Invalid, string, i
        prev = None
        if pos == len(string) >= 2 \
                and string[-1] == " " and string[-2].isdigit():
            string = string[:-1] + ", "
            pos += 1
        for valuestr in re_custom_sep.split(string.strip()):
            try:
                value = float(valuestr)
            except ValueError:
                return QValidator.Intermediate, string, pos
            if prev is not None and value <= prev:
                return QValidator.Intermediate, string, pos
            prev = value
        return QValidator.Acceptable, string, pos

    @staticmethod
    def show_tip(
            widget: QWidget, pos: QPoint, text: str, timeout=-1,
            textFormat=Qt.AutoText, wordWrap=None):
        """Show a tooltip; used for invalid custom thresholds"""
        propname = __name__ + "::show_tip_qlabel"
        if timeout < 0:
            timeout = widget.toolTipDuration()
        if timeout < 0:
            timeout = 5000 + 40 * max(0, len(text) - 100)
        tip = widget.property(propname)
        if not text and tip is None:
            return

        def hide():
            w = tip.parent()
            w.setProperty(propname, None)
            tip.timer.stop()
            tip.close()
            tip.deleteLater()

        if not isinstance(tip, QLabel):
            tip = QLabel(objectName="tip-label", focusPolicy=Qt.NoFocus)
            tip.setBackgroundRole(QPalette.ToolTipBase)
            tip.setForegroundRole(QPalette.ToolTipText)
            tip.setPalette(QToolTip.palette())
            tip.setFont(QApplication.font("QTipLabel"))
            tip.setContentsMargins(2, 2, 2, 2)
            tip.timer = QTimer(tip, singleShot=True, objectName="hide-timer")
            tip.timer.timeout.connect(hide)
            widget.setProperty(propname, tip)
            tip.setParent(widget, Qt.ToolTip)

        tip.setText(text)
        tip.setTextFormat(textFormat)
        if wordWrap is None:
            wordWrap = textFormat != Qt.PlainText
        tip.setWordWrap(wordWrap)

        if not text:
            hide()
        else:
            tip.timer.start(timeout)
            tip.show()
            tip.move(pos)


# These are no longer used, but needed for loading and migrating old pickles.
# We insert them into namespace instead of normally defining them, in order
# to hide it from IDE's and avoid mistakenly using them.
# pylint: disable=wrong-import-position,wrong-import-order
from collections import namedtuple
globals().update(dict(
    DState=namedtuple(
        "DState",
        ["method",  # discretization method
         "points",  # induced cut points
         "disc_var"],
        defaults=(None, None)  # induced discretized variable
    ),
    Default=namedtuple("Default", ["method"]),
    Leave=namedtuple("Leave", []),
    MDL=namedtuple("MDL", []),
    EqualFreq=namedtuple("EqualFreq", ["k"]),
    EqualWidth=namedtuple("EqualWidth", ["k"]),
    Remove=namedtuple("Remove", []),
    Custom=namedtuple("Custom", ["points"])
))


class OWDiscretize(widget.OWWidget):
    # pylint: disable=too-many-instance-attributes
    name = "Discretize"
    description = "Discretize numeric variables"
    category = "Transform"
    icon = "icons/Discretize.svg"
    keywords = ["bin", "categorical", "nominal", "ordinal"]
    priority = 2130

    class Inputs:
        data = Input("Data", Table, doc="Input data table")

    class Outputs:
        data = Output("Data", Table, doc="Table with categorical features")

    settings_version = 3

    #: Default setting (key DefaultKey) and specific settings for variables;
    # if variable is not in the dict, it uses default
    var_hints: Dict[KeyType, VarHint] = Setting(
        {DefaultKey: DefaultHint}, schema_only=True)
    autosend = Setting(True)

    want_main_area = False

    def __init__(self):
        super().__init__()

        #: input data
        self.data = None
        #: Cached discretized variables
        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}

        # Indicates that buttons, spins, edit and combos are being changed
        # programmatically (when interface is changed due to selection change),
        # so this should not trigger update of hints and invalidation of
        # discretization in `self.discretized_vars`.
        self.__interface_update = False

        box = gui.hBox(self.controlArea, True, spacing=8)
        self._create_var_list(box)
        self._create_buttons(box)
        gui.auto_apply(self.buttonsArea, self, "autosend")
        gui.rubber(self.buttonsArea)

        self.varview.select_default()

    def _create_var_list(self, box):
        """Create list view with variables"""
        # If we decide to not elide, remove the `uniformItemSize` argument
        self.varview = ListViewSearch(
            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)
        self.varview.setModel(
            DiscDomainModel(
                valid_types=(ContinuousVariable, TimeVariable),
                order=DiscDomainModel.MIXED
            ))
        self.varview.selectionModel().selectionChanged.connect(
            self._var_selection_changed)
        self.varview.default_view.selectionModel().selectionChanged.connect(
            self._default_selected)
        self._update_default_model()
        box.layout().addWidget(self.varview)

    def _create_buttons(self, box):
        """Create radio buttons"""
        def intspin():
            s = QSpinBox(self)
            s.setMinimum(2)
            s.setMaximum(10)
            s.setFixedWidth(60)
            s.setAlignment(Qt.AlignRight)
            s.setContentsMargins(0, 0, 0, 0)
            return s, s.valueChanged

        def widthline(validator):
            s = QLineEdit(self)
            s.setFixedWidth(60)
            s.setAlignment(Qt.AlignRight)
            s.setValidator(validator)
            s.setContentsMargins(0, 0, 0, 0)
            return s, s.textChanged

        def manual_cut_editline(text="", enabled=True) -> QLineEdit:
            edit = QLineEdit(
                text=text,
                placeholderText="e.g. 0.0, 0.5, 1.0",
                toolTip='<p style="white-space:pre">' +
                        'Enter cut points as a comma-separate list of \n'
                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',
                enabled=enabled,
            )
            edit.setValidator(IncreasingNumbersListValidator())
            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)

            @edit.textChanged.connect
            def update():
                validator = edit.validator()
                if validator is not None and edit.text().strip():
                    state, _, _ = validator.validate(edit.text(), 0)
                else:
                    state = QValidator.Acceptable
                palette = edit.palette()
                colors = {
                    QValidator.Intermediate: (Qt.yellow, Qt.black),
                    QValidator.Invalid: (Qt.red, Qt.black),
                }.get(state, None)
                if colors is None:
                    palette = QPalette()
                else:
                    palette.setColor(QPalette.Base, colors[0])
                    palette.setColor(QPalette.Text, colors[1])

                cr = edit.cursorRect()
                p = edit.mapToGlobal(cr.bottomRight())
                edit.setPalette(palette)
                if state != QValidator.Acceptable and edit.isVisible():
                    validator.show_tip(edit, p, edit.toolTip(),
                                       textFormat=Qt.RichText)
                else:
                    validator.show_tip(edit, p, "")
            return edit, edit.textChanged

        children = []

        def button(id_, *controls, stretch=True):
            layout = QHBoxLayout()
            desc = Options[id_]
            button = QRadioButton(desc.label)
            button.setToolTip(desc.tooltip)
            self.button_group.addButton(button, id_)
            layout.addWidget(button)
            if controls:
                if stretch:
                    layout.addStretch(1)
                for c, signal in controls:
                    layout.addWidget(c)
                    if signal is not None:
                        @signal.connect
                        def arg_changed():
                            self.button_group.button(id_).setChecked(True)
                            self.update_hints(id_)

            children.append(layout)
            button_box.layout().addLayout(layout)
            return (*controls, (None, ))[0][0]

        button_box = gui.vBox(box)
        button_box.layout().setSpacing(0)
        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))
        self.button_group = QButtonGroup(self)
        self.button_group.idClicked.connect(self.update_hints)

        button(Methods.Keep)
        button(Methods.Remove)

        self.binning_spin = button(Methods.Binning, intspin())
        validator = QDoubleValidator()
        validator.setBottom(0)
        self.width_line = button(Methods.FixedWidth, widthline(validator))

        self.width_time_unit = u = QComboBox(self)
        u.setContentsMargins(0, 0, 0, 0)
        u.addItems([unit + "(s)" for unit in time_units])
        validator = QIntValidator()
        validator.setBottom(1)
        self.width_time_line = button(Methods.FixedWidthTime,
                                      widthline(validator),
                                      (u, u.currentTextChanged))

        self.freq_spin = button(Methods.EqualFreq, intspin())
        self.width_spin = button(Methods.EqualWidth, intspin())
        button(Methods.MDL)

        self.copy_to_custom = FixedSizeButton(
            text="CC", toolTip="Copy the current cut points to manual mode")
        self.copy_to_custom.clicked.connect(self._copy_to_manual)
        self.threshold_line = button(Methods.Custom,
                                     manual_cut_editline(),
                                     (self.copy_to_custom, None),
                                     stretch=False)
        button(Methods.Default)
        maxheight = max(w.sizeHint().height() for w in children)
        for w in children:
            w.itemAt(0).widget().setFixedHeight(maxheight)
        button_box.layout().addStretch(1)

    def _update_default_model(self):
        """Update data in the model showing default settings"""
        model = self.varview.default_view.model()
        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)

    def _set_mdl_button(self):
        """Disable MDL discretization for data with non-discrete class"""
        mdl_button = self.button_group.button(Methods.MDL)
        if self.data is None or self.data.domain.has_discrete_class:
            mdl_button.setEnabled(True)
        else:
            if mdl_button.isChecked():
                self._check_button(Methods.Keep, True)
            mdl_button.setEnabled(False)

    def _check_button(self, method_id: Methods, checked: bool):
        """Checks the given button"""
        self.button_group.button(method_id).setChecked(checked)

    def _uncheck_all_buttons(self):
        """Uncheck all radio buttons"""
        group = self.button_group
        button = group.checkedButton()
        if button is not None:
            group.setExclusive(False)
            button.setChecked(False)
            group.setExclusive(True)

    def _set_radio_enabled(self, method_id: Methods, value: bool):
        """Enable/disable radio button and related controls"""
        if self.button_group.button(method_id).isChecked() and not value:
            self._uncheck_all_buttons()
        self.button_group.button(method_id).setEnabled(value)
        for control_name in Options[method_id].controls:
            getattr(self, control_name).setEnabled(value)

    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:
        """Return parameters from controls pertaining to the given method"""
        controls = Options[method_id].controls
        values = []
        for control_name in controls:
            control = getattr(self, control_name)
            if isinstance(control, QSpinBox):
                values.append(control.value())
            elif isinstance(control, QComboBox):
                values.append(control.currentIndex())
            else:
                values.append(control.text())
        return tuple(values)

    def _set_values(self, method_id: Methods,
                    values: Tuple[Union[str, int, float]]):
        """
        Set controls pertaining to the given method to parameters from hint
        """
        controls = Options[method_id].controls
        for control_name, value in zip(controls, values):
            control = getattr(self, control_name)
            if isinstance(control, QSpinBox):
                control.setValue(value)
            elif isinstance(control, QComboBox):
                control.setCurrentIndex(value)
            else:
                control.setText(value)

    def varkeys_for_selection(self) -> List[KeyType]:
        """
        Return list of KeyType's for selected variables (for indexing var_hints)

        If 'Default settings' are selected, this returns DefaultKey
        """
        model = self.varview.model()
        varkeys = [variable_key(model[index.row()])
                   for index in self.varview.selectionModel().selectedRows()]
        return varkeys or [DefaultKey]  # default settings are selected

    def update_hints(self, method_id: Methods):
        """
        Callback for radio buttons and for controls regulating parameters

        This function:
        - updates `var_hints` for all selected methods
        - invalidates (removes) `discretized_vars` for affected variables
        - calls _update_discretizations to compute and commit new discretization
        - calls deferred commit

        Data for list view models is updated in _update_discretizations
        """
        if self.__interface_update:
            return

        method_id = Methods(method_id)
        args = self._get_values(method_id)
        keys = self.varkeys_for_selection()
        if method_id == Methods.Default:
            for key in keys:
                if key in self.var_hints:
                    del self.var_hints[key]
        else:
            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))
        if keys == [DefaultKey]:
            invalidate = set(self.discretized_vars) - set(self.var_hints)
        else:
            invalidate = keys
        for key in invalidate:
            del self.discretized_vars[key]

        if keys == [DefaultKey]:
            self._update_default_model()
        self._update_discretizations()
        self.commit.deferred()

    def _update_discretizations(self):
        """
        Compute invalidated (missing) discretizations

        Also set data for list view models for all invalidated variables
        """
        if self.data is None:
            return

        default_hint = self.var_hints[DefaultKey]
        model = self.varview.model()
        for index, var in enumerate(model):
            key = variable_key(var)
            if key in self.discretized_vars:
                continue  # still valid
            var_hint = self.var_hints.get(key)
            points, dvar = self._discretize_var(var, var_hint or default_hint)
            self.discretized_vars[key] = dvar
            values = getattr(dvar, "values", ())
            model.setData(model.index(index),
                          DiscDesc(var_hint, points, values),
                          Qt.UserRole)

    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \
        -> Tuple[str, Optional[Variable]]:
        """
        Discretize using method and data in the hint.

        Returns a description (list of points or error/warning) and a
        - discrete variable
        - same variable (if kept numeric)
        - None (if removed or errored)
        """
        if isinstance(var, TimeVariable):
            if hint.method_id in (Methods.FixedWidth, Methods.Custom):
                return ": <keep, time var>", var
        else:
            if hint.method_id == Methods.FixedWidthTime:
                return ": <keep, not time>", var

        function = Options[hint.method_id].function
        dvar = function(self.data, var, *hint.args)
        if isinstance(dvar, str):
            return f" <{dvar}>", None  # error
        if dvar is None:
            return "", None  # removed
        elif dvar is var:
            return "", var  # no transformation
        thresholds = dvar.compute_value.points
        if len(thresholds) == 0:
            return " <removed>", None
        return ": " + ", ".join(map(var.repr_val, thresholds)), dvar

    def _copy_to_manual(self):
        """
        Callback for 'CC' button

        Sets selected variables' method to "Custom" and copies thresholds
        to their VarHints. Variables that are not discretized (for any reason)
        are skipped.

        Discretizations are invalidated and then updated
        (`_update_discretizations`).

        If all selected variables have the same thresholds, it copies it to
        the line edit. Otherwise it unchecks all radio buttons to keep the
        interface consistent.
        """
        varkeys = self.varkeys_for_selection()
        texts = set()
        for key in varkeys:
            dvar = self.discretized_vars.get(key)
            fmt = self.data.domain[key[0]].repr_val
            if isinstance(dvar, DiscreteVariable):
                text = ", ".join(map(fmt, dvar.compute_value.points))
                texts.add(text)
                self.var_hints[key] = VarHint(Methods.Custom, (text, ))
                del self.discretized_vars[key]
        try:
            self.__interface_update = True
            if len(texts) == 1:
                self.threshold_line.setText(texts.pop())
            else:
                self._uncheck_all_buttons()
        finally:
            self.__interface_update = False
        self._update_discretizations()
        self.commit.deferred()

    def _default_selected(self, selected):
        """Callback for selecting 'Default setting'"""
        if not selected:
            # Prevent infinite recursion (with _var_selection_changed)
            return
        self.varview.selectionModel().clearSelection()
        self._update_interface()

        set_enabled = self._set_radio_enabled
        set_enabled(Methods.Default, False)
        set_enabled(Methods.FixedWidth, True)
        set_enabled(Methods.FixedWidthTime, True)
        set_enabled(Methods.Custom, True)

    def _var_selection_changed(self, _):
        """Callback for changed selection in listview with variables"""
        selected = self.varview.selectionModel().selectedIndexes()
        if not selected:
            # Prevent infinite recursion (with _default_selected)
            return
        self.varview.default_view.selectionModel().clearSelection()
        self._update_interface()

        set_enabled = self._set_radio_enabled
        vars_ = [self.data.domain[name]
                 for name, _ in self.varkeys_for_selection()]
        no_time = not any(isinstance(var, TimeVariable) for var in vars_)
        all_time = all(isinstance(var, TimeVariable) for var in vars_)
        set_enabled(Methods.Default, True)
        set_enabled(Methods.FixedWidth, no_time)
        set_enabled(Methods.Custom, no_time)
        set_enabled(Methods.FixedWidthTime, all_time)

    def _update_interface(self):
        """
        Update the user interface according to selection

        - If VarHints for all selected variables are the same, check the
          corresponding radio button and fill the corresponding controls;
        - otherwise, uncheck all radios.
        """
        if self.__interface_update:
            return

        try:
            self.__interface_update = True
            keys = self.varkeys_for_selection()
            mset = list(unique_everseen(map(self.var_hints.get, keys)))
            if len(mset) != 1:
                self._uncheck_all_buttons()
                return

            if mset == [None]:
                method_id, args = Methods.Default, ()
            else:
                method_id, args = mset.pop()
            self._check_button(method_id, True)
            self._set_values(method_id, args)
        finally:
            self.__interface_update = False

    @Inputs.data
    def set_data(self, data: Optional[Table]):
        self.discretized_vars = {}
        self.data = data
        self.varview.model().set_domain(None if data is None else data.domain)
        self._update_discretizations()
        self._update_default_model()
        self._set_mdl_button()
        self.commit.now()

    @gui.deferred
    def commit(self):
        if self.data is None:
            self.Outputs.data.send(None)
            return

        def part(variables: List[Variable]) -> List[Variable]:
            return [dvar
                    for dvar in (self.discretized_vars.get(variable_key(v), v)
                                 for v in variables)
                    if dvar]

        d = self.data.domain
        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))
        output = self.data.transform(domain)
        self.Outputs.data.send(output)

    def send_report(self):
        dmodel = self.varview.default_view.model()
        desc = dmodel.data(dmodel.index(0))
        self.report_items((tuple(desc.split(": ", maxsplit=1)), ))
        model = self.varview.model()
        reported = []
        for row in range(model.rowCount()):
            name = model[row].name
            desc = model.data(model.index(row), Qt.UserRole)
            if desc.hint is not None:
                name = f"{name} ({format_desc(desc.hint)})"
            reported.append((name, ', '.join(desc.values)))
        self.report_items("Variables", reported)

    @classmethod
    def migrate_settings(cls, settings, version):
        if version is None or version < 2:
            # was stored as int indexing Methods (but offset by 1)
            default = settings.pop("default_method", 0)
            default = Methods(default + 1)
            settings["default_method_name"] = default.name

        if version is None or version < 3:
            method_name = settings.pop("default_method_name",
                                       DefaultHint.method_id.name)
            k = settings.pop("default_k", 3)
            cut_points = settings.pop("default_cutpoints", ())

            method_id = getattr(Methods, method_name)
            if method_id in (Methods.EqualFreq, Methods.EqualWidth):
                args = (k, )
            elif method_id == Methods.Custom:
                args = (cut_points, )
            else:
                args = ()
            default_hint = VarHint(method_id, args)
            var_hints = {DefaultKey: default_hint}
            for context in settings.pop("context_settings", []):
                values = context.values
                if "saved_var_states" not in values:
                    continue
                var_states, _ = values.pop("saved_var_states")
                for (tpe, name), dstate in var_states.items():
                    key = (name, tpe == 4)  # time variable == 4
                    method = dstate.method
                    method_name = type(method).__name__.replace("Leave", "Keep")
                    if method_name == "Default":
                        continue
                    if method_name == "Custom":
                        args = (", ".join(f"{x:g}" for x in method.points), )
                    else:
                        args = tuple(method)
                    var_hints[key] = VarHint(getattr(Methods, method_name), args)
            settings["var_hints"] = var_hints


if __name__ == "__main__":  # pragma: no cover
    #WidgetPreview(OWDiscretize).run(Table("/Users/janez/Downloads/banking-crises.tab"))
    WidgetPreview(OWDiscretize).run(Table("heart_disease"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** `copy_to_custom` Button Remains Enabled When Default Discretization is Selected

**Description:**
In the OWDiscretize widget, selecting the default discretization method should disable the `copy_to_custom` button to prevent users from copying settings that are not applicable. However, the button remains enabled even when the default method is chosen, leading to unexpected behavior.

**Example Scenario:**

When multiple variables are selected and the default discretization method is applied, the `copy_to_custom` button should be disabled. Instead, it stays enabled, which can confuse users and allow inappropriate actions.

```python
w = OWDiscretize()
w.var_hints = {
    DefaultKey: DefaultHint,
    ("x", False): VarHint(Methods.FixedWidth, ("10",)),
    ("y", False): VarHint(Methods.FixedWidth, ("10",)),
    ("z", False): VarHint(Methods.FixedWidth, ("5",)),
    ("t", False): VarHint(Methods.Binning, (5, ))
}

select_rows(w.varview, (3, 4))
# Expected: copy_to_custom button should be disabled
# Actual: copy_to_custom button is still enabled
```

**Expected Behavior:**
The `copy_to_custom` button should be disabled when the default discretization method is selected to ensure the interface behaves correctly and prevents unintended actions.

**Actual Behavior:**
The `copy_to_custom` button remains enabled even when the default discretization method is selected, causing inconsistent UI behavior and potential misuse.

[/ISSUE]
{
    "TestOWDiscretize.test_all": "PASSED",
    "TestOWDiscretize.test_change_selection_update_interface": "PASSED",
    "TestOWDiscretize.test_copy_to_manual": "PASSED",
    "TestOWDiscretize.test_discretize_var": "PASSED",
    "TestOWDiscretize.test_empty_data": "PASSED",
    "TestOWDiscretize.test_get_values": "PASSED",
    "TestOWDiscretize.test_image_export": "PASSED",
    "TestOWDiscretize.test_migration_2_3": "PASSED",
    "TestOWDiscretize.test_minimum_size": "PASSED",
    "TestOWDiscretize.test_msg_base_class": "PASSED",
    "TestOWDiscretize.test_report": "PASSED",
    "TestOWDiscretize.test_set_values": "PASSED",
    "TestOWDiscretize.test_update_discretizations": "PASSED",
    "TestOWDiscretize.test_update_hints": "PASSED",
    "TestOWDiscretize.test_varkeys_for_selection": "PASSED",
    "TestValidator.test_validate": "PASSED",
    "TestModels.test_delegate": "PASSED",
    "TestModels.test_image_export": "PASSED",
    "TestModels.test_layout": "PASSED",
    "TestModels.test_minimum_size": "PASSED",
    "TestModels.test_model": "PASSED",
    "TestModels.test_msg_base_class": "PASSED",
    "TestDefaultDiscModel.test_counts": "PASSED",
    "TestDefaultDiscModel.test_data": "PASSED",
    "TestUtils.test_custom_discretization": "PASSED",
    "TestUtils.test_fixed_time_width_disc": "PASSED",
    "TestUtils.test_fixed_width_disc": "PASSED",
    "TestUtils.test_format_desc": "PASSED",
    "TestUtils.test_mdl_discretization": "PASSED",
    "TestUtils.test_show_tip": "PASSED",
    "TestUtils.test_var_key": "PASSED"
}
diff --git a/Orange/widgets/data/owdiscretize.py b/Orange/widgets/data/owdiscretize.py
index 625582096..298bb5828 100644
--- a/Orange/widgets/data/owdiscretize.py
+++ b/Orange/widgets/data/owdiscretize.py
@@ -882,6 +882,7 @@ class OWDiscretize(widget.OWWidget):
         set_enabled(Methods.FixedWidth, True)
         set_enabled(Methods.FixedWidthTime, True)
         set_enabled(Methods.Custom, True)
+        self.copy_to_custom.setEnabled(False)
 
     def _var_selection_changed(self, _):
         """Callback for changed selection in listview with variables"""
@@ -900,6 +901,7 @@ class OWDiscretize(widget.OWWidget):
         set_enabled(Methods.Default, True)
         set_enabled(Methods.FixedWidth, no_time)
         set_enabled(Methods.Custom, no_time)
+        self.copy_to_custom.setEnabled(no_time)
         set_enabled(Methods.FixedWidthTime, all_time)
 
     def _update_interface(self):
@@ -937,6 +939,7 @@ class OWDiscretize(widget.OWWidget):
         self.varview.model().set_domain(None if data is None else data.domain)
         self._update_discretizations()
         self._update_default_model()
+        self.varview.select_default()
         self._set_mdl_button()
         self.commit.now()
 

{"Orange/widgets/data/owdiscretize.py": "import re\nimport html\nfrom enum import IntEnum\nfrom typing import Optional, Tuple, Union, Callable, NamedTuple, Dict, List\n\nfrom AnyQt.QtCore import (\n    Qt, QTimer, QPoint, QItemSelectionModel, QSize, QAbstractListModel)\nfrom AnyQt.QtGui import (\n    QValidator, QPalette, QDoubleValidator, QIntValidator,  QColor)\nfrom AnyQt.QtWidgets import (\n    QListView, QHBoxLayout, QStyledItemDelegate, QButtonGroup, QWidget,\n    QLineEdit, QToolTip, QLabel, QApplication,\n    QSpinBox, QSizePolicy, QRadioButton, QComboBox)\n\nfrom orangewidget.settings import Setting\nfrom orangewidget.utils import listview\n\nfrom Orange.data import (\n    Variable, ContinuousVariable, DiscreteVariable, TimeVariable, Domain, Table)\nimport Orange.preprocess.discretize as disc\nfrom Orange.widgets import widget, gui\nfrom Orange.widgets.utils import unique_everseen\nfrom Orange.widgets.utils.itemmodels import DomainModel\nfrom Orange.widgets.utils.widgetpreview import WidgetPreview\nfrom Orange.widgets.widget import Input, Output\nfrom Orange.widgets.data.oweditdomain import FixedSizeButton\n\n\nre_custom_sep = re.compile(r\"\\s*,\\s*\")\ntime_units = [\"year\", \"month\", \"day\", \"week\", \"hour\", \"minute\", \"second\"]\nINVALID_WIDTH = \"invalid width\"\nTOO_MANY_INTERVALS = \"too many intervals\"\n\n\ndef _fixed_width_discretization(\n        data: Table,\n        var: Union[ContinuousVariable, str, int],\n        width: str) -> Union[DiscreteVariable, str]:\n    \"\"\"\n    Discretize numeric variable with fixed bin width. Used in method definition.\n\n    Width is given as string (coming from line edit). The labels for the new\n    variable will have the same number of digits; this is more appropriate\n    than the number of digits in the original variable, which may be too large.\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        width: interval width\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    digits = len(width) - width.index(\".\") - 1 if \".\" in width else 0\n    try:\n        width = float(width)\n    except ValueError:\n        return INVALID_WIDTH\n    if width <= 0:\n        return INVALID_WIDTH\n    try:\n        return disc.FixedWidth(width, digits)(data, var)\n    except disc.TooManyIntervals:\n        return TOO_MANY_INTERVALS\n\n\n# pylint: disable=invalid-name\ndef _fixed_time_width_discretization(\n        data: Table,\n        var: Union[TimeVariable, str, int],\n        width: str, unit: int) -> Union[DiscreteVariable]:\n    \"\"\"\n    Discretize time variable with fixed bin width. Used in method definition.\n\n    Width is given as string (coming from line edit).\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        width: interval width\n        unit: 0 = year, 1 = month, 2 = week, 3 = day, 4 = hour, 5 = min, 6 = sec\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    try:\n        width = int(width)\n    except ValueError:\n        return INVALID_WIDTH\n    if width <= 0:\n        return INVALID_WIDTH\n    if unit == 3:  # week\n        width *= 7\n    unit -= unit >= 3\n    try:\n        return disc.FixedTimeWidth(width, unit)(data, var)\n    except disc.TooManyIntervals:\n        return TOO_MANY_INTERVALS\n\n\ndef _mdl_discretization(\n        data: Table,\n        var: Union[ContinuousVariable, str, int]) -> Union[DiscreteVariable, str]:\n    if not data.domain.has_discrete_class:\n        return \"no discrete class\"\n    return disc.EntropyMDL()(data, var)\n\n\ndef _custom_discretization(\n        _,\n        var: Union[ContinuousVariable, str, int],\n        points: str) -> Union[DiscreteVariable, str]:\n    \"\"\"\n    Discretize variable using custom thresholds. Used in method definition.\n\n    Thresholds are given as string (coming from line edit).\n\n    Args:\n        data: data used to deduce the interval of values\n        var: variable to discretize\n        points: thresholds\n\n    Returns:\n        Discrete variable, if successful; a string with error otherwise\n    \"\"\"\n    try:\n        cuts = [float(x) for x in re_custom_sep.split(points.strip())]\n    except ValueError:\n        cuts = []\n    if any(x >= y for x, y in zip(cuts, cuts[1:])):\n        cuts = []\n    if not cuts:\n        return \"invalid cuts\"\n    return disc.Discretizer.create_discretized_var(var, cuts)\n\n\nclass Methods(IntEnum):\n    # pylint: disable=invalid-name\n    Default, Keep, MDL, EqualFreq, EqualWidth, Remove, Custom, Binning, \\\n        FixedWidth, FixedWidthTime = range(10)\n\n\nclass MethodDesc(NamedTuple):\n    \"\"\"\n    Definitions of all methods; used for creation of interface and calling\n    \"\"\"\n    id_: Methods  # Method id\n    label: str  # Label used for radio button\n    short_desc: str  # Short descriptions for list views\n    tooltip: str  # Tooltip for radio button\n    # Discretization function, see, e.g. fixed_width_discretization\n    function: Optional[Callable[..., Union[DiscreteVariable, str]]]\n    controls: Tuple[str, ...] = ()  # Widget attributes with related ux controls\n\n\nOptions: Dict[Methods, MethodDesc] = {\n    method.id_: method\n    for method in (\n        MethodDesc(Methods.Default,\n                   \"Use default setting\", \"default\",\n                   \"Treat the variable as defined in 'default setting'\",\n                   None,\n                   ()),\n        MethodDesc(Methods.Keep,\n                   \"Keep numeric\", \"keep\",\n                   \"Keep the variable as is\",\n                   lambda data, var: var,\n                   ()),\n        MethodDesc(Methods.MDL,\n                   \"Entropy vs. MDL\", \"entropy\",\n                   \"Split values until MDL exceeds the entropy (Fayyad-Irani)\\n\"\n                   \"(requires discrete class variable)\",\n                   _mdl_discretization,\n                   ()),\n        MethodDesc(Methods.EqualFreq,\n                   \"Equal frequency, intervals: \", \"equal freq, k={}\",\n                   \"Create bins with same number of instances\",\n                   lambda data, var, k: disc.EqualFreq(k)(data, var),\n                   (\"freq_spin\", )),\n        MethodDesc(Methods.EqualWidth,\n                   \"Equal width, intervals: \", \"equal width, k={}\",\n                   \"Create bins of the same width\",\n                   lambda data, var, k: disc.EqualWidth(k)(data, var),\n                   (\"width_spin\", )),\n        MethodDesc(Methods.Remove,\n                   \"Remove\", \"remove\",\n                   \"Remove variable\",\n                   lambda *_: None,\n                   ()),\n        MethodDesc(Methods.Binning,\n                   \"Natural binning, desired bins: \", \"binning, desired={}\",\n                   \"Create bins with nice thresholds; \"\n                   \"try matching desired number of bins\",\n                   lambda data, var, nbins: disc.Binning(nbins)(data, var),\n                   (\"binning_spin\", )),\n        MethodDesc(Methods.FixedWidth,\n                   \"Fixed width: \", \"fixed width {}\",\n                   \"Create bins with the given width (not for time variables)\",\n                   _fixed_width_discretization,\n                   (\"width_line\", )),\n        MethodDesc(Methods.FixedWidthTime,\n                   \"Time interval: \", \"time interval, {} {}\",\n                   \"Create bins with the give width (for time variables)\",\n                   _fixed_time_width_discretization,\n                   (\"width_time_line\", \"width_time_unit\")),\n        MethodDesc(Methods.Custom,\n                   \"Custom: \", \"custom: {}\",\n                   \"Use manually specified thresholds\",\n                   _custom_discretization,\n                   (\"threshold_line\", ))\n    )\n}\n\n\nclass VarHint(NamedTuple):\n    \"\"\"Description for settings\"\"\"\n    method_id: Methods\n    args: Tuple[Union[str, float, int]]\n\n\nclass DiscDesc(NamedTuple):\n    \"\"\"Data for list view model\"\"\"\n    hint: VarHint\n    points: str\n    values: Tuple[str]\n\n\nKeyType = Optional[Tuple[str, bool]]\nDefaultHint = VarHint(Methods.Keep, ())\nDefaultKey = None\n\n\ndef variable_key(var: ContinuousVariable) -> KeyType:\n    \"\"\"Key for that variable in var_hints and discretized_vars\"\"\"\n    return var.name, isinstance(var, TimeVariable)\n\n\nclass ListViewSearch(listview.ListViewSearch):\n    \"\"\"\n    A list view with two components shown above it:\n    - a listview containing a single item representing default settings\n    - a filter for search\n\n    The class is based on listview.ListViewSearch and needs to have the same\n    name in order to override its private method __layout.\n\n    Inherited __init__ calls __layout, so `default_view` must be constructed\n    there. Construction before calling super().__init__ doesn't work because\n    PyQt does not allow it.\n    \"\"\"\n    class DiscDelegate(QStyledItemDelegate):\n        \"\"\"\n        A delegate that shows items (variables) with specific settings in bold\n        \"\"\"\n        def initStyleOption(self, option, index):\n            super().initStyleOption(option, index)\n            option.font.setBold(index.data(Qt.UserRole).hint is not None)\n\n    def __init__(self, *args, **kwargs):\n        self.default_view = None\n        super().__init__(preferred_size=QSize(350, -1), *args, **kwargs)\n        self.setItemDelegate(self.DiscDelegate(self))\n\n    def select_default(self):\n        \"\"\"Select the item representing default settings\"\"\"\n        index = self.default_view.model().index(0)\n        self.default_view.selectionModel().select(\n            index, QItemSelectionModel.Select)\n\n    # pylint: disable=unused-private-member\n    def __layout(self):\n        if self.default_view is None:  # __layout was called from __init__\n            view = self.default_view = QListView(self)\n            view.setModel(DefaultDiscModel())\n            view.verticalScrollBar().setDisabled(True)\n            view.horizontalScrollBar().setDisabled(True)\n            view.setHorizontalScrollBarPolicy(\n                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)\n            view.setVerticalScrollBarPolicy(\n                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)\n            font = view.font()\n            font.setBold(True)\n            view.setFont(font)\n        else:\n            view = self.default_view\n\n        # Put the list view with default on top\n        margins = self.viewportMargins()\n        def_height = view.sizeHintForRow(0) + 2 * view.spacing() + 2\n        view.setGeometry(0, 0, self.geometry().width(), def_height)\n        view.setFixedHeight(def_height)\n\n        # Then search\n        search = self.__search\n        src_height = search.sizeHint().height()\n        size = self.size()\n        search.setGeometry(0, def_height + 2, size.width(), src_height)\n\n        # Then the real list view\n        margins.setTop(def_height + 2 + src_height)\n        self.setViewportMargins(margins)\n\n\ndef format_desc(hint: VarHint) -> str:\n    \"\"\"Describe the method and its parameters; used in list views and report\"\"\"\n    if hint is None:\n        return Options[Methods.Default].short_desc\n    desc = Options[hint.method_id].short_desc\n    if hint.method_id == Methods.FixedWidthTime:\n        width, unit = hint.args\n        unit = time_units[unit]\n        try:\n            width = int(width)\n        except ValueError:\n            unit += \"(s)\"\n        else:\n            if width != 1:\n                unit += \"s\"\n        return desc.format(width, unit)\n    return desc.format(*hint.args)\n\n\nclass DiscDomainModel(DomainModel):\n    \"\"\"\n    Domain model that adds description of discretization methods and thresholds\n\n    Also provides a tooltip that shows bins, that is, labels of the discretized\n    variable.\n    \"\"\"\n    def data(self, index, role=Qt.DisplayRole):\n        if role == Qt.ToolTipRole:\n            var = self[index.row()]\n            data = index.data(Qt.UserRole)\n            tip = f\"<b>{var.name}: </b>\"\n            values = map(html.escape, data.values)\n            if not data.values:\n                return None\n            if len(data.values) <= 3:\n                return f'<p style=\"white-space:pre\">{tip}' \\\n                       f'{\",&nbsp;&nbsp;\".join(values)}</p>'\n            else:\n                return tip + \"<br/>\" \\\n                    + \"\".join(f\"- {value}<br/>\" for value in values)\n        value = super().data(index, role)\n        if role == Qt.DisplayRole:\n            hint, points, values = index.data(Qt.UserRole)\n            value += f\" ({format_desc(hint)}){points}\"\n        return value\n\n\nclass DefaultDiscModel(QAbstractListModel):\n    \"\"\"\n    A model used for showing \"Default settings\" above the list view with var\n    \"\"\"\n    icon = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        if DefaultDiscModel.icon is None:\n            DefaultDiscModel.icon = gui.createAttributePixmap(\n                \"\u2605\", QColor(0, 0, 0, 0), Qt.black)\n        self.hint: VarHint = DefaultHint\n\n    @staticmethod\n    def rowCount(parent):\n        return 0 if parent.isValid() else 1\n\n    @staticmethod\n    def columnCount(parent):\n        return 0 if parent.isValid() else 1\n\n    def data(self, _, role=Qt.DisplayRole):\n        if role == Qt.DisplayRole:\n            return \"Default setting: \" + format_desc(self.hint)\n        elif role == Qt.DecorationRole:\n            return DefaultDiscModel.icon\n        elif role == Qt.ToolTipRole:\n            return \"Default setting for variables without specific setings\"\n        return None\n\n    def setData(self, index, value, role=Qt.DisplayRole):\n        if role == Qt.UserRole:\n            self.hint = value\n            self.dataChanged.emit(index, index)\n\n\nclass IncreasingNumbersListValidator(QValidator):\n    \"\"\"\n    A validator for custom thresholds\n\n    Requires a string with increasing comma-separated values. If the string\n    ends with number followed by space, it inserts a comma.\n    \"\"\"\n    @staticmethod\n    def validate(string: str, pos: int) -> Tuple[QValidator.State, str, int]:\n        for i, c in enumerate(string, start=1):\n            if c not in \"+-., 0123456789\":\n                return QValidator.Invalid, string, i\n        prev = None\n        if pos == len(string) >= 2 \\\n                and string[-1] == \" \" and string[-2].isdigit():\n            string = string[:-1] + \", \"\n            pos += 1\n        for valuestr in re_custom_sep.split(string.strip()):\n            try:\n                value = float(valuestr)\n            except ValueError:\n                return QValidator.Intermediate, string, pos\n            if prev is not None and value <= prev:\n                return QValidator.Intermediate, string, pos\n            prev = value\n        return QValidator.Acceptable, string, pos\n\n    @staticmethod\n    def show_tip(\n            widget: QWidget, pos: QPoint, text: str, timeout=-1,\n            textFormat=Qt.AutoText, wordWrap=None):\n        \"\"\"Show a tooltip; used for invalid custom thresholds\"\"\"\n        propname = __name__ + \"::show_tip_qlabel\"\n        if timeout < 0:\n            timeout = widget.toolTipDuration()\n        if timeout < 0:\n            timeout = 5000 + 40 * max(0, len(text) - 100)\n        tip = widget.property(propname)\n        if not text and tip is None:\n            return\n\n        def hide():\n            w = tip.parent()\n            w.setProperty(propname, None)\n            tip.timer.stop()\n            tip.close()\n            tip.deleteLater()\n\n        if not isinstance(tip, QLabel):\n            tip = QLabel(objectName=\"tip-label\", focusPolicy=Qt.NoFocus)\n            tip.setBackgroundRole(QPalette.ToolTipBase)\n            tip.setForegroundRole(QPalette.ToolTipText)\n            tip.setPalette(QToolTip.palette())\n            tip.setFont(QApplication.font(\"QTipLabel\"))\n            tip.setContentsMargins(2, 2, 2, 2)\n            tip.timer = QTimer(tip, singleShot=True, objectName=\"hide-timer\")\n            tip.timer.timeout.connect(hide)\n            widget.setProperty(propname, tip)\n            tip.setParent(widget, Qt.ToolTip)\n\n        tip.setText(text)\n        tip.setTextFormat(textFormat)\n        if wordWrap is None:\n            wordWrap = textFormat != Qt.PlainText\n        tip.setWordWrap(wordWrap)\n\n        if not text:\n            hide()\n        else:\n            tip.timer.start(timeout)\n            tip.show()\n            tip.move(pos)\n\n\n# These are no longer used, but needed for loading and migrating old pickles.\n# We insert them into namespace instead of normally defining them, in order\n# to hide it from IDE's and avoid mistakenly using them.\n# pylint: disable=wrong-import-position,wrong-import-order\nfrom collections import namedtuple\nglobals().update(dict(\n    DState=namedtuple(\n        \"DState\",\n        [\"method\",  # discretization method\n         \"points\",  # induced cut points\n         \"disc_var\"],\n        defaults=(None, None)  # induced discretized variable\n    ),\n    Default=namedtuple(\"Default\", [\"method\"]),\n    Leave=namedtuple(\"Leave\", []),\n    MDL=namedtuple(\"MDL\", []),\n    EqualFreq=namedtuple(\"EqualFreq\", [\"k\"]),\n    EqualWidth=namedtuple(\"EqualWidth\", [\"k\"]),\n    Remove=namedtuple(\"Remove\", []),\n    Custom=namedtuple(\"Custom\", [\"points\"])\n))\n\n\nclass OWDiscretize(widget.OWWidget):\n    # pylint: disable=too-many-instance-attributes\n    name = \"Discretize\"\n    description = \"Discretize numeric variables\"\n    category = \"Transform\"\n    icon = \"icons/Discretize.svg\"\n    keywords = [\"bin\", \"categorical\", \"nominal\", \"ordinal\"]\n    priority = 2130\n\n    class Inputs:\n        data = Input(\"Data\", Table, doc=\"Input data table\")\n\n    class Outputs:\n        data = Output(\"Data\", Table, doc=\"Table with categorical features\")\n\n    settings_version = 3\n\n    #: Default setting (key DefaultKey) and specific settings for variables;\n    # if variable is not in the dict, it uses default\n    var_hints: Dict[KeyType, VarHint] = Setting(\n        {DefaultKey: DefaultHint}, schema_only=True)\n    autosend = Setting(True)\n\n    want_main_area = False\n\n    def __init__(self):\n        super().__init__()\n\n        #: input data\n        self.data = None\n        #: Cached discretized variables\n        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}\n\n        # Indicates that buttons, spins, edit and combos are being changed\n        # programmatically (when interface is changed due to selection change),\n        # so this should not trigger update of hints and invalidation of\n        # discretization in `self.discretized_vars`.\n        self.__interface_update = False\n\n        box = gui.hBox(self.controlArea, True, spacing=8)\n        self._create_var_list(box)\n        self._create_buttons(box)\n        gui.auto_apply(self.buttonsArea, self, \"autosend\")\n        gui.rubber(self.buttonsArea)\n\n        self.varview.select_default()\n\n    def _create_var_list(self, box):\n        \"\"\"Create list view with variables\"\"\"\n        # If we decide to not elide, remove the `uniformItemSize` argument\n        self.varview = ListViewSearch(\n            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)\n        self.varview.setModel(\n            DiscDomainModel(\n                valid_types=(ContinuousVariable, TimeVariable),\n                order=DiscDomainModel.MIXED\n            ))\n        self.varview.selectionModel().selectionChanged.connect(\n            self._var_selection_changed)\n        self.varview.default_view.selectionModel().selectionChanged.connect(\n            self._default_selected)\n        self._update_default_model()\n        box.layout().addWidget(self.varview)\n\n    def _create_buttons(self, box):\n        \"\"\"Create radio buttons\"\"\"\n        def intspin():\n            s = QSpinBox(self)\n            s.setMinimum(2)\n            s.setMaximum(10)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.valueChanged\n\n        def widthline(validator):\n            s = QLineEdit(self)\n            s.setFixedWidth(60)\n            s.setAlignment(Qt.AlignRight)\n            s.setValidator(validator)\n            s.setContentsMargins(0, 0, 0, 0)\n            return s, s.textChanged\n\n        def manual_cut_editline(text=\"\", enabled=True) -> QLineEdit:\n            edit = QLineEdit(\n                text=text,\n                placeholderText=\"e.g. 0.0, 0.5, 1.0\",\n                toolTip='<p style=\"white-space:pre\">' +\n                        'Enter cut points as a comma-separate list of \\n'\n                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',\n                enabled=enabled,\n            )\n            edit.setValidator(IncreasingNumbersListValidator())\n            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)\n\n            @edit.textChanged.connect\n            def update():\n                validator = edit.validator()\n                if validator is not None and edit.text().strip():\n                    state, _, _ = validator.validate(edit.text(), 0)\n                else:\n                    state = QValidator.Acceptable\n                palette = edit.palette()\n                colors = {\n                    QValidator.Intermediate: (Qt.yellow, Qt.black),\n                    QValidator.Invalid: (Qt.red, Qt.black),\n                }.get(state, None)\n                if colors is None:\n                    palette = QPalette()\n                else:\n                    palette.setColor(QPalette.Base, colors[0])\n                    palette.setColor(QPalette.Text, colors[1])\n\n                cr = edit.cursorRect()\n                p = edit.mapToGlobal(cr.bottomRight())\n                edit.setPalette(palette)\n                if state != QValidator.Acceptable and edit.isVisible():\n                    validator.show_tip(edit, p, edit.toolTip(),\n                                       textFormat=Qt.RichText)\n                else:\n                    validator.show_tip(edit, p, \"\")\n            return edit, edit.textChanged\n\n        children = []\n\n        def button(id_, *controls, stretch=True):\n            layout = QHBoxLayout()\n            desc = Options[id_]\n            button = QRadioButton(desc.label)\n            button.setToolTip(desc.tooltip)\n            self.button_group.addButton(button, id_)\n            layout.addWidget(button)\n            if controls:\n                if stretch:\n                    layout.addStretch(1)\n                for c, signal in controls:\n                    layout.addWidget(c)\n                    if signal is not None:\n                        @signal.connect\n                        def arg_changed():\n                            self.button_group.button(id_).setChecked(True)\n                            self.update_hints(id_)\n\n            children.append(layout)\n            button_box.layout().addLayout(layout)\n            return (*controls, (None, ))[0][0]\n\n        button_box = gui.vBox(box)\n        button_box.layout().setSpacing(0)\n        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))\n        self.button_group = QButtonGroup(self)\n        self.button_group.idClicked.connect(self.update_hints)\n\n        button(Methods.Keep)\n        button(Methods.Remove)\n\n        self.binning_spin = button(Methods.Binning, intspin())\n        validator = QDoubleValidator()\n        validator.setBottom(0)\n        self.width_line = button(Methods.FixedWidth, widthline(validator))\n\n        self.width_time_unit = u = QComboBox(self)\n        u.setContentsMargins(0, 0, 0, 0)\n        u.addItems([unit + \"(s)\" for unit in time_units])\n        validator = QIntValidator()\n        validator.setBottom(1)\n        self.width_time_line = button(Methods.FixedWidthTime,\n                                      widthline(validator),\n                                      (u, u.currentTextChanged))\n\n        self.freq_spin = button(Methods.EqualFreq, intspin())\n        self.width_spin = button(Methods.EqualWidth, intspin())\n        button(Methods.MDL)\n\n        self.copy_to_custom = FixedSizeButton(\n            text=\"CC\", toolTip=\"Copy the current cut points to manual mode\")\n        self.copy_to_custom.clicked.connect(self._copy_to_manual)\n        self.threshold_line = button(Methods.Custom,\n                                     manual_cut_editline(),\n                                     (self.copy_to_custom, None),\n                                     stretch=False)\n        button(Methods.Default)\n        maxheight = max(w.sizeHint().height() for w in children)\n        for w in children:\n            w.itemAt(0).widget().setFixedHeight(maxheight)\n        button_box.layout().addStretch(1)\n\n    def _update_default_model(self):\n        \"\"\"Update data in the model showing default settings\"\"\"\n        model = self.varview.default_view.model()\n        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)\n\n    def _set_mdl_button(self):\n        \"\"\"Disable MDL discretization for data with non-discrete class\"\"\"\n        mdl_button = self.button_group.button(Methods.MDL)\n        if self.data is None or self.data.domain.has_discrete_class:\n            mdl_button.setEnabled(True)\n        else:\n            if mdl_button.isChecked():\n                self._check_button(Methods.Keep, True)\n            mdl_button.setEnabled(False)\n\n    def _check_button(self, method_id: Methods, checked: bool):\n        \"\"\"Checks the given button\"\"\"\n        self.button_group.button(method_id).setChecked(checked)\n\n    def _uncheck_all_buttons(self):\n        \"\"\"Uncheck all radio buttons\"\"\"\n        group = self.button_group\n        button = group.checkedButton()\n        if button is not None:\n            group.setExclusive(False)\n            button.setChecked(False)\n            group.setExclusive(True)\n\n    def _set_radio_enabled(self, method_id: Methods, value: bool):\n        \"\"\"Enable/disable radio button and related controls\"\"\"\n        if self.button_group.button(method_id).isChecked() and not value:\n            self._uncheck_all_buttons()\n        self.button_group.button(method_id).setEnabled(value)\n        for control_name in Options[method_id].controls:\n            getattr(self, control_name).setEnabled(value)\n\n    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:\n        \"\"\"Return parameters from controls pertaining to the given method\"\"\"\n        controls = Options[method_id].controls\n        values = []\n        for control_name in controls:\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                values.append(control.value())\n            elif isinstance(control, QComboBox):\n                values.append(control.currentIndex())\n            else:\n                values.append(control.text())\n        return tuple(values)\n\n    def _set_values(self, method_id: Methods,\n                    values: Tuple[Union[str, int, float]]):\n        \"\"\"\n        Set controls pertaining to the given method to parameters from hint\n        \"\"\"\n        controls = Options[method_id].controls\n        for control_name, value in zip(controls, values):\n            control = getattr(self, control_name)\n            if isinstance(control, QSpinBox):\n                control.setValue(value)\n            elif isinstance(control, QComboBox):\n                control.setCurrentIndex(value)\n            else:\n                control.setText(value)\n\n    def varkeys_for_selection(self) -> List[KeyType]:\n        \"\"\"\n        Return list of KeyType's for selected variables (for indexing var_hints)\n\n        If 'Default settings' are selected, this returns DefaultKey\n        \"\"\"\n        model = self.varview.model()\n        varkeys = [variable_key(model[index.row()])\n                   for index in self.varview.selectionModel().selectedRows()]\n        return varkeys or [DefaultKey]  # default settings are selected\n\n    def update_hints(self, method_id: Methods):\n        \"\"\"\n        Callback for radio buttons and for controls regulating parameters\n\n        This function:\n        - updates `var_hints` for all selected methods\n        - invalidates (removes) `discretized_vars` for affected variables\n        - calls _update_discretizations to compute and commit new discretization\n        - calls deferred commit\n\n        Data for list view models is updated in _update_discretizations\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        method_id = Methods(method_id)\n        args = self._get_values(method_id)\n        keys = self.varkeys_for_selection()\n        if method_id == Methods.Default:\n            for key in keys:\n                if key in self.var_hints:\n                    del self.var_hints[key]\n        else:\n            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))\n        if keys == [DefaultKey]:\n            invalidate = set(self.discretized_vars) - set(self.var_hints)\n        else:\n            invalidate = keys\n        for key in invalidate:\n            del self.discretized_vars[key]\n\n        if keys == [DefaultKey]:\n            self._update_default_model()\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _update_discretizations(self):\n        \"\"\"\n        Compute invalidated (missing) discretizations\n\n        Also set data for list view models for all invalidated variables\n        \"\"\"\n        if self.data is None:\n            return\n\n        default_hint = self.var_hints[DefaultKey]\n        model = self.varview.model()\n        for index, var in enumerate(model):\n            key = variable_key(var)\n            if key in self.discretized_vars:\n                continue  # still valid\n            var_hint = self.var_hints.get(key)\n            points, dvar = self._discretize_var(var, var_hint or default_hint)\n            self.discretized_vars[key] = dvar\n            values = getattr(dvar, \"values\", ())\n            model.setData(model.index(index),\n                          DiscDesc(var_hint, points, values),\n                          Qt.UserRole)\n\n    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \\\n        -> Tuple[str, Optional[Variable]]:\n        \"\"\"\n        Discretize using method and data in the hint.\n\n        Returns a description (list of points or error/warning) and a\n        - discrete variable\n        - same variable (if kept numeric)\n        - None (if removed or errored)\n        \"\"\"\n        if isinstance(var, TimeVariable):\n            if hint.method_id in (Methods.FixedWidth, Methods.Custom):\n                return \": <keep, time var>\", var\n        else:\n            if hint.method_id == Methods.FixedWidthTime:\n                return \": <keep, not time>\", var\n\n        function = Options[hint.method_id].function\n        dvar = function(self.data, var, *hint.args)\n        if isinstance(dvar, str):\n            return f\" <{dvar}>\", None  # error\n        if dvar is None:\n            return \"\", None  # removed\n        elif dvar is var:\n            return \"\", var  # no transformation\n        thresholds = dvar.compute_value.points\n        if len(thresholds) == 0:\n            return \" <removed>\", None\n        return \": \" + \", \".join(map(var.repr_val, thresholds)), dvar\n\n    def _copy_to_manual(self):\n        \"\"\"\n        Callback for 'CC' button\n\n        Sets selected variables' method to \"Custom\" and copies thresholds\n        to their VarHints. Variables that are not discretized (for any reason)\n        are skipped.\n\n        Discretizations are invalidated and then updated\n        (`_update_discretizations`).\n\n        If all selected variables have the same thresholds, it copies it to\n        the line edit. Otherwise it unchecks all radio buttons to keep the\n        interface consistent.\n        \"\"\"\n        varkeys = self.varkeys_for_selection()\n        texts = set()\n        for key in varkeys:\n            dvar = self.discretized_vars.get(key)\n            fmt = self.data.domain[key[0]].repr_val\n            if isinstance(dvar, DiscreteVariable):\n                text = \", \".join(map(fmt, dvar.compute_value.points))\n                texts.add(text)\n                self.var_hints[key] = VarHint(Methods.Custom, (text, ))\n                del self.discretized_vars[key]\n        try:\n            self.__interface_update = True\n            if len(texts) == 1:\n                self.threshold_line.setText(texts.pop())\n            else:\n                self._uncheck_all_buttons()\n        finally:\n            self.__interface_update = False\n        self._update_discretizations()\n        self.commit.deferred()\n\n    def _default_selected(self, selected):\n        \"\"\"Callback for selecting 'Default setting'\"\"\"\n        if not selected:\n            # Prevent infinite recursion (with _var_selection_changed)\n            return\n        self.varview.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        set_enabled(Methods.Default, False)\n        set_enabled(Methods.FixedWidth, True)\n        set_enabled(Methods.FixedWidthTime, True)\n        set_enabled(Methods.Custom, True)\n\n    def _var_selection_changed(self, _):\n        \"\"\"Callback for changed selection in listview with variables\"\"\"\n        selected = self.varview.selectionModel().selectedIndexes()\n        if not selected:\n            # Prevent infinite recursion (with _default_selected)\n            return\n        self.varview.default_view.selectionModel().clearSelection()\n        self._update_interface()\n\n        set_enabled = self._set_radio_enabled\n        vars_ = [self.data.domain[name]\n                 for name, _ in self.varkeys_for_selection()]\n        no_time = not any(isinstance(var, TimeVariable) for var in vars_)\n        all_time = all(isinstance(var, TimeVariable) for var in vars_)\n        set_enabled(Methods.Default, True)\n        set_enabled(Methods.FixedWidth, no_time)\n        set_enabled(Methods.Custom, no_time)\n        set_enabled(Methods.FixedWidthTime, all_time)\n\n    def _update_interface(self):\n        \"\"\"\n        Update the user interface according to selection\n\n        - If VarHints for all selected variables are the same, check the\n          corresponding radio button and fill the corresponding controls;\n        - otherwise, uncheck all radios.\n        \"\"\"\n        if self.__interface_update:\n            return\n\n        try:\n            self.__interface_update = True\n            keys = self.varkeys_for_selection()\n            mset = list(unique_everseen(map(self.var_hints.get, keys)))\n            if len(mset) != 1:\n                self._uncheck_all_buttons()\n                return\n\n            if mset == [None]:\n                method_id, args = Methods.Default, ()\n            else:\n                method_id, args = mset.pop()\n            self._check_button(method_id, True)\n            self._set_values(method_id, args)\n        finally:\n            self.__interface_update = False\n\n    @Inputs.data\n    def set_data(self, data: Optional[Table]):\n        self.discretized_vars = {}\n        self.data = data\n        self.varview.model().set_domain(None if data is None else data.domain)\n        self._update_discretizations()\n        self._update_default_model()\n        self._set_mdl_button()\n        self.commit.now()\n\n    @gui.deferred\n    def commit(self):\n        if self.data is None:\n            self.Outputs.data.send(None)\n            return\n\n        def part(variables: List[Variable]) -> List[Variable]:\n            return [dvar\n                    for dvar in (self.discretized_vars.get(variable_key(v), v)\n                                 for v in variables)\n                    if dvar]\n\n        d = self.data.domain\n        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))\n        output = self.data.transform(domain)\n        self.Outputs.data.send(output)\n\n    def send_report(self):\n        dmodel = self.varview.default_view.model()\n        desc = dmodel.data(dmodel.index(0))\n        self.report_items((tuple(desc.split(\": \", maxsplit=1)), ))\n        model = self.varview.model()\n        reported = []\n        for row in range(model.rowCount()):\n            name = model[row].name\n            desc = model.data(model.index(row), Qt.UserRole)\n            if desc.hint is not None:\n                name = f\"{name} ({format_desc(desc.hint)})\"\n            reported.append((name, ', '.join(desc.values)))\n        self.report_items(\"Variables\", reported)\n\n    @classmethod\n    def migrate_settings(cls, settings, version):\n        if version is None or version < 2:\n            # was stored as int indexing Methods (but offset by 1)\n            default = settings.pop(\"default_method\", 0)\n            default = Methods(default + 1)\n            settings[\"default_method_name\"] = default.name\n\n        if version is None or version < 3:\n            method_name = settings.pop(\"default_method_name\",\n                                       DefaultHint.method_id.name)\n            k = settings.pop(\"default_k\", 3)\n            cut_points = settings.pop(\"default_cutpoints\", ())\n\n            method_id = getattr(Methods, method_name)\n            if method_id in (Methods.EqualFreq, Methods.EqualWidth):\n                args = (k, )\n            elif method_id == Methods.Custom:\n                args = (cut_points, )\n            else:\n                args = ()\n            default_hint = VarHint(method_id, args)\n            var_hints = {DefaultKey: default_hint}\n            for context in settings.pop(\"context_settings\", []):\n                values = context.values\n                if \"saved_var_states\" not in values:\n                    continue\n                var_states, _ = values.pop(\"saved_var_states\")\n                for (tpe, name), dstate in var_states.items():\n                    key = (name, tpe == 4)  # time variable == 4\n                    method = dstate.method\n                    method_name = type(method).__name__.replace(\"Leave\", \"Keep\")\n                    if method_name == \"Default\":\n                        continue\n                    if method_name == \"Custom\":\n                        args = (\", \".join(f\"{x:g}\" for x in method.points), )\n                    else:\n                        args = tuple(method)\n                    var_hints[key] = VarHint(getattr(Methods, method_name), args)\n            settings[\"var_hints\"] = var_hints\n\n\nif __name__ == \"__main__\":  # pragma: no cover\n    #WidgetPreview(OWDiscretize).run(Table(\"/Users/janez/Downloads/banking-crises.tab\"))\n    WidgetPreview(OWDiscretize).run(Table(\"heart_disease\"))\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `copy_to_custom` Button Remains Enabled When Default Discretization is Selected

**Description:**
In the OWDiscretize widget, selecting the default discretization method should disable the `copy_to_custom` button to prevent users from copying settings that are not applicable. However, the button remains enabled even when the default method is chosen, leading to unexpected behavior.

**Example Scenario:**

When multiple variables are selected and the default discretization method is applied, the `copy_to_custom` button should be disabled. Instead, it stays enabled, which can confuse users and allow inappropriate actions.

```python
w = OWDiscretize()
w.var_hints = {
    DefaultKey: DefaultHint,
    ("x", False): VarHint(Methods.FixedWidth, ("10",)),
    ("y", False): VarHint(Methods.FixedWidth, ("10",)),
    ("z", False): VarHint(Methods.FixedWidth, ("5",)),
    ("t", False): VarHint(Methods.Binning, (5, ))
}

select_rows(w.varview, (3, 4))
# Expected: copy_to_custom button should be disabled
# Actual: copy_to_custom button is still enabled
```

**Expected Behavior:**
The `copy_to_custom` button should be disabled when the default discretization method is selected to ensure the interface behaves correctly and prevents unintended actions.

**Actual Behavior:**
The `copy_to_custom` button remains enabled even when the default discretization method is selected, causing inconsistent UI behavior and potential misuse.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### Orange/widgets/data/owdiscretize.py
import re
import html
from enum import IntEnum
from typing import Optional, Tuple, Union, Callable, NamedTuple, Dict, List

from AnyQt.QtCore import (
    Qt, QTimer, QPoint, QItemSelectionModel, QSize, QAbstractListModel)
from AnyQt.QtGui import (
    QValidator, QPalette, QDoubleValidator, QIntValidator,  QColor)
from AnyQt.QtWidgets import (
    QListView, QHBoxLayout, QStyledItemDelegate, QButtonGroup, QWidget,
    QLineEdit, QToolTip, QLabel, QApplication,
    QSpinBox, QSizePolicy, QRadioButton, QComboBox)

from orangewidget.settings import Setting
from orangewidget.utils import listview

from Orange.data import (
    Variable, ContinuousVariable, DiscreteVariable, TimeVariable, Domain, Table)
import Orange.preprocess.discretize as disc
from Orange.widgets import widget, gui
from Orange.widgets.utils import unique_everseen
from Orange.widgets.utils.itemmodels import DomainModel
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.widget import Input, Output
from Orange.widgets.data.oweditdomain import FixedSizeButton


re_custom_sep = re.compile(r"\s*,\s*")
time_units = ["year", "month", "day", "week", "hour", "minute", "second"]
INVALID_WIDTH = "invalid width"
TOO_MANY_INTERVALS = "too many intervals"


def _fixed_width_discretization(
        data: Table,
        var: Union[ContinuousVariable, str, int],
        width: str) -> Union[DiscreteVariable, str]:
    """
    Discretize numeric variable with fixed bin width. Used in method definition.

    Width is given as string (coming from line edit). The labels for the new
    variable will have the same number of digits; this is more appropriate
    than the number of digits in the original variable, which may be too large.

    Args:
        data: data used to deduce the interval of values
        var: variable to discretize
        width: interval width

    Returns:
        Discrete variable, if successful; a string with error otherwise
    """
    digits = len(width) - width.index(".") - 1 if "." in width else 0
    try:
        width = float(width)
    except ValueError:
        return INVALID_WIDTH
    if width <= 0:
        return INVALID_WIDTH
    try:
        return disc.FixedWidth(width, digits)(data, var)
    except disc.TooManyIntervals:
        return TOO_MANY_INTERVALS


# pylint: disable=invalid-name
def _fixed_time_width_discretization(
        data: Table,
        var: Union[TimeVariable, str, int],
        width: str, unit: int) -> Union[DiscreteVariable]:
    """
    Discretize time variable with fixed bin width. Used in method definition.

    Width is given as string (coming from line edit).

    Args:
        data: data used to deduce the interval of values
        var: variable to discretize
        width: interval width
        unit: 0 = year, 1 = month, 2 = week, 3 = day, 4 = hour, 5 = min, 6 = sec

    Returns:
        Discrete variable, if successful; a string with error otherwise
    """
    try:
        width = int(width)
    except ValueError:
        return INVALID_WIDTH
    if width <= 0:
        return INVALID_WIDTH
    if unit == 3:  # week
        width *= 7
    unit -= unit >= 3
    try:
        return disc.FixedTimeWidth(width, unit)(data, var)
    except disc.TooManyIntervals:
        return TOO_MANY_INTERVALS


def _mdl_discretization(
        data: Table,
        var: Union[ContinuousVariable, str, int]) -> Union[DiscreteVariable, str]:
    if not data.domain.has_discrete_class:
        return "no discrete class"
    return disc.EntropyMDL()(data, var)


def _custom_discretization(
        _,
        var: Union[ContinuousVariable, str, int],
        points: str) -> Union[DiscreteVariable, str]:
    """
    Discretize variable using custom thresholds. Used in method definition.

    Thresholds are given as string (coming from line edit).

    Args:
        data: data used to deduce the interval of values
        var: variable to discretize
        points: thresholds

    Returns:
        Discrete variable, if successful; a string with error otherwise
    """
    try:
        cuts = [float(x) for x in re_custom_sep.split(points.strip())]
    except ValueError:
        cuts = []
    if any(x >= y for x, y in zip(cuts, cuts[1:])):
        cuts = []
    if not cuts:
        return "invalid cuts"
    return disc.Discretizer.create_discretized_var(var, cuts)


class Methods(IntEnum):
    # pylint: disable=invalid-name
    Default, Keep, MDL, EqualFreq, EqualWidth, Remove, Custom, Binning, \
        FixedWidth, FixedWidthTime = range(10)


class MethodDesc(NamedTuple):
    """
    Definitions of all methods; used for creation of interface and calling
    """
    id_: Methods  # Method id
    label: str  # Label used for radio button
    short_desc: str  # Short descriptions for list views
    tooltip: str  # Tooltip for radio button
    # Discretization function, see, e.g. fixed_width_discretization
    function: Optional[Callable[..., Union[DiscreteVariable, str]]]
    controls: Tuple[str, ...] = ()  # Widget attributes with related ux controls


Options: Dict[Methods, MethodDesc] = {
    method.id_: method
    for method in (
        MethodDesc(Methods.Default,
                   "Use default setting", "default",
                   "Treat the variable as defined in 'default setting'",
                   None,
                   ()),
        MethodDesc(Methods.Keep,
                   "Keep numeric", "keep",
                   "Keep the variable as is",
                   lambda data, var: var,
                   ()),
        MethodDesc(Methods.MDL,
                   "Entropy vs. MDL", "entropy",
                   "Split values until MDL exceeds the entropy (Fayyad-Irani)\n"
                   "(requires discrete class variable)",
                   _mdl_discretization,
                   ()),
        MethodDesc(Methods.EqualFreq,
                   "Equal frequency, intervals: ", "equal freq, k={}",
                   "Create bins with same number of instances",
                   lambda data, var, k: disc.EqualFreq(k)(data, var),
                   ("freq_spin", )),
        MethodDesc(Methods.EqualWidth,
                   "Equal width, intervals: ", "equal width, k={}",
                   "Create bins of the same width",
                   lambda data, var, k: disc.EqualWidth(k)(data, var),
                   ("width_spin", )),
        MethodDesc(Methods.Remove,
                   "Remove", "remove",
                   "Remove variable",
                   lambda *_: None,
                   ()),
        MethodDesc(Methods.Binning,
                   "Natural binning, desired bins: ", "binning, desired={}",
                   "Create bins with nice thresholds; "
                   "try matching desired number of bins",
                   lambda data, var, nbins: disc.Binning(nbins)(data, var),
                   ("binning_spin", )),
        MethodDesc(Methods.FixedWidth,
                   "Fixed width: ", "fixed width {}",
                   "Create bins with the given width (not for time variables)",
                   _fixed_width_discretization,
                   ("width_line", )),
        MethodDesc(Methods.FixedWidthTime,
                   "Time interval: ", "time interval, {} {}",
                   "Create bins with the give width (for time variables)",
                   _fixed_time_width_discretization,
                   ("width_time_line", "width_time_unit")),
        MethodDesc(Methods.Custom,
                   "Custom: ", "custom: {}",
                   "Use manually specified thresholds",
                   _custom_discretization,
                   ("threshold_line", ))
    )
}


class VarHint(NamedTuple):
    """Description for settings"""
    method_id: Methods
    args: Tuple[Union[str, float, int]]


class DiscDesc(NamedTuple):
    """Data for list view model"""
    hint: VarHint
    points: str
    values: Tuple[str]


KeyType = Optional[Tuple[str, bool]]
DefaultHint = VarHint(Methods.Keep, ())
DefaultKey = None


def variable_key(var: ContinuousVariable) -> KeyType:
    """Key for that variable in var_hints and discretized_vars"""
    return var.name, isinstance(var, TimeVariable)


class ListViewSearch(listview.ListViewSearch):
    """
    A list view with two components shown above it:
    - a listview containing a single item representing default settings
    - a filter for search

    The class is based on listview.ListViewSearch and needs to have the same
    name in order to override its private method __layout.

    Inherited __init__ calls __layout, so `default_view` must be constructed
    there. Construction before calling super().__init__ doesn't work because
    PyQt does not allow it.
    """
    class DiscDelegate(QStyledItemDelegate):
        """
        A delegate that shows items (variables) with specific settings in bold
        """
        def initStyleOption(self, option, index):
            super().initStyleOption(option, index)
            option.font.setBold(index.data(Qt.UserRole).hint is not None)

    def __init__(self, *args, **kwargs):
        self.default_view = None
        super().__init__(preferred_size=QSize(350, -1), *args, **kwargs)
        self.setItemDelegate(self.DiscDelegate(self))

    def select_default(self):
        """Select the item representing default settings"""
        index = self.default_view.model().index(0)
        self.default_view.selectionModel().select(
            index, QItemSelectionModel.Select)

    # pylint: disable=unused-private-member
    def __layout(self):
        if self.default_view is None:  # __layout was called from __init__
            view = self.default_view = QListView(self)
            view.setModel(DefaultDiscModel())
            view.verticalScrollBar().setDisabled(True)
            view.horizontalScrollBar().setDisabled(True)
            view.setHorizontalScrollBarPolicy(
                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
            view.setVerticalScrollBarPolicy(
                Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
            font = view.font()
            font.setBold(True)
            view.setFont(font)
        else:
            view = self.default_view

        # Put the list view with default on top
        margins = self.viewportMargins()
        def_height = view.sizeHintForRow(0) + 2 * view.spacing() + 2
        view.setGeometry(0, 0, self.geometry().width(), def_height)
        view.setFixedHeight(def_height)

        # Then search
        search = self.__search
        src_height = search.sizeHint().height()
        size = self.size()
        search.setGeometry(0, def_height + 2, size.width(), src_height)

        # Then the real list view
        margins.setTop(def_height + 2 + src_height)
        self.setViewportMargins(margins)


def format_desc(hint: VarHint) -> str:
    """Describe the method and its parameters; used in list views and report"""
    if hint is None:
        return Options[Methods.Default].short_desc
    desc = Options[hint.method_id].short_desc
    if hint.method_id == Methods.FixedWidthTime:
        width, unit = hint.args
        unit = time_units[unit]
        try:
            width = int(width)
        except ValueError:
            unit += "(s)"
        else:
            if width != 1:
                unit += "s"
        return desc.format(width, unit)
    return desc.format(*hint.args)


class DiscDomainModel(DomainModel):
    """
    Domain model that adds description of discretization methods and thresholds

    Also provides a tooltip that shows bins, that is, labels of the discretized
    variable.
    """
    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.ToolTipRole:
            var = self[index.row()]
            data = index.data(Qt.UserRole)
            tip = f"<b>{var.name}: </b>"
            values = map(html.escape, data.values)
            if not data.values:
                return None
            if len(data.values) <= 3:
                return f'<p style="white-space:pre">{tip}' \
                       f'{",&nbsp;&nbsp;".join(values)}</p>'
            else:
                return tip + "<br/>" \
                    + "".join(f"- {value}<br/>" for value in values)
        value = super().data(index, role)
        if role == Qt.DisplayRole:
            hint, points, values = index.data(Qt.UserRole)
            value += f" ({format_desc(hint)}){points}"
        return value


class DefaultDiscModel(QAbstractListModel):
    """
    A model used for showing "Default settings" above the list view with var
    """
    icon = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if DefaultDiscModel.icon is None:
            DefaultDiscModel.icon = gui.createAttributePixmap(
                "★", QColor(0, 0, 0, 0), Qt.black)
        self.hint: VarHint = DefaultHint

    @staticmethod
    def rowCount(parent):
        return 0 if parent.isValid() else 1

    @staticmethod
    def columnCount(parent):
        return 0 if parent.isValid() else 1

    def data(self, _, role=Qt.DisplayRole):
        if role == Qt.DisplayRole:
            return "Default setting: " + format_desc(self.hint)
        elif role == Qt.DecorationRole:
            return DefaultDiscModel.icon
        elif role == Qt.ToolTipRole:
            return "Default setting for variables without specific setings"
        return None

    def setData(self, index, value, role=Qt.DisplayRole):
        if role == Qt.UserRole:
            self.hint = value
            self.dataChanged.emit(index, index)


class IncreasingNumbersListValidator(QValidator):
    """
    A validator for custom thresholds

    Requires a string with increasing comma-separated values. If the string
    ends with number followed by space, it inserts a comma.
    """
    @staticmethod
    def validate(string: str, pos: int) -> Tuple[QValidator.State, str, int]:
        for i, c in enumerate(string, start=1):
            if c not in "+-., 0123456789":
                return QValidator.Invalid, string, i
        prev = None
        if pos == len(string) >= 2 \
                and string[-1] == " " and string[-2].isdigit():
            string = string[:-1] + ", "
            pos += 1
        for valuestr in re_custom_sep.split(string.strip()):
            try:
                value = float(valuestr)
            except ValueError:
                return QValidator.Intermediate, string, pos
            if prev is not None and value <= prev:
                return QValidator.Intermediate, string, pos
            prev = value
        return QValidator.Acceptable, string, pos

    @staticmethod
    def show_tip(
            widget: QWidget, pos: QPoint, text: str, timeout=-1,
            textFormat=Qt.AutoText, wordWrap=None):
        """Show a tooltip; used for invalid custom thresholds"""
        propname = __name__ + "::show_tip_qlabel"
        if timeout < 0:
            timeout = widget.toolTipDuration()
        if timeout < 0:
            timeout = 5000 + 40 * max(0, len(text) - 100)
        tip = widget.property(propname)
        if not text and tip is None:
            return

        def hide():
            w = tip.parent()
            w.setProperty(propname, None)
            tip.timer.stop()
            tip.close()
            tip.deleteLater()

        if not isinstance(tip, QLabel):
            tip = QLabel(objectName="tip-label", focusPolicy=Qt.NoFocus)
            tip.setBackgroundRole(QPalette.ToolTipBase)
            tip.setForegroundRole(QPalette.ToolTipText)
            tip.setPalette(QToolTip.palette())
            tip.setFont(QApplication.font("QTipLabel"))
            tip.setContentsMargins(2, 2, 2, 2)
            tip.timer = QTimer(tip, singleShot=True, objectName="hide-timer")
            tip.timer.timeout.connect(hide)
            widget.setProperty(propname, tip)
            tip.setParent(widget, Qt.ToolTip)

        tip.setText(text)
        tip.setTextFormat(textFormat)
        if wordWrap is None:
            wordWrap = textFormat != Qt.PlainText
        tip.setWordWrap(wordWrap)

        if not text:
            hide()
        else:
            tip.timer.start(timeout)
            tip.show()
            tip.move(pos)


# These are no longer used, but needed for loading and migrating old pickles.
# We insert them into namespace instead of normally defining them, in order
# to hide it from IDE's and avoid mistakenly using them.
# pylint: disable=wrong-import-position,wrong-import-order
from collections import namedtuple
globals().update(dict(
    DState=namedtuple(
        "DState",
        ["method",  # discretization method
         "points",  # induced cut points
         "disc_var"],
        defaults=(None, None)  # induced discretized variable
    ),
    Default=namedtuple("Default", ["method"]),
    Leave=namedtuple("Leave", []),
    MDL=namedtuple("MDL", []),
    EqualFreq=namedtuple("EqualFreq", ["k"]),
    EqualWidth=namedtuple("EqualWidth", ["k"]),
    Remove=namedtuple("Remove", []),
    Custom=namedtuple("Custom", ["points"])
))


class OWDiscretize(widget.OWWidget):
    # pylint: disable=too-many-instance-attributes
    name = "Discretize"
    description = "Discretize numeric variables"
    category = "Transform"
    icon = "icons/Discretize.svg"
    keywords = ["bin", "categorical", "nominal", "ordinal"]
    priority = 2130

    class Inputs:
        data = Input("Data", Table, doc="Input data table")

    class Outputs:
        data = Output("Data", Table, doc="Table with categorical features")

    settings_version = 3

    #: Default setting (key DefaultKey) and specific settings for variables;
    # if variable is not in the dict, it uses default
    var_hints: Dict[KeyType, VarHint] = Setting(
        {DefaultKey: DefaultHint}, schema_only=True)
    autosend = Setting(True)

    want_main_area = False

    def __init__(self):
        super().__init__()

        #: input data
        self.data = None
        #: Cached discretized variables
        self.discretized_vars: Dict[KeyType, DiscreteVariable] = {}

        # Indicates that buttons, spins, edit and combos are being changed
        # programmatically (when interface is changed due to selection change),
        # so this should not trigger update of hints and invalidation of
        # discretization in `self.discretized_vars`.
        self.__interface_update = False

        box = gui.hBox(self.controlArea, True, spacing=8)
        self._create_var_list(box)
        self._create_buttons(box)
        gui.auto_apply(self.buttonsArea, self, "autosend")
        gui.rubber(self.buttonsArea)

        self.varview.select_default()

    def _create_var_list(self, box):
        """Create list view with variables"""
        # If we decide to not elide, remove the `uniformItemSize` argument
        self.varview = ListViewSearch(
            selectionMode=QListView.ExtendedSelection, uniformItemSizes=True)
        self.varview.setModel(
            DiscDomainModel(
                valid_types=(ContinuousVariable, TimeVariable),
                order=DiscDomainModel.MIXED
            ))
        self.varview.selectionModel().selectionChanged.connect(
            self._var_selection_changed)
        self.varview.default_view.selectionModel().selectionChanged.connect(
            self._default_selected)
        self._update_default_model()
        box.layout().addWidget(self.varview)

    def _create_buttons(self, box):
        """Create radio buttons"""
        def intspin():
            s = QSpinBox(self)
            s.setMinimum(2)
            s.setMaximum(10)
            s.setFixedWidth(60)
            s.setAlignment(Qt.AlignRight)
            s.setContentsMargins(0, 0, 0, 0)
            return s, s.valueChanged

        def widthline(validator):
            s = QLineEdit(self)
            s.setFixedWidth(60)
            s.setAlignment(Qt.AlignRight)
            s.setValidator(validator)
            s.setContentsMargins(0, 0, 0, 0)
            return s, s.textChanged

        def manual_cut_editline(text="", enabled=True) -> QLineEdit:
            edit = QLineEdit(
                text=text,
                placeholderText="e.g. 0.0, 0.5, 1.0",
                toolTip='<p style="white-space:pre">' +
                        'Enter cut points as a comma-separate list of \n'
                        'strictly increasing numbers e.g. 0.0, 0.5, 1.0).</p>',
                enabled=enabled,
            )
            edit.setValidator(IncreasingNumbersListValidator())
            edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)

            @edit.textChanged.connect
            def update():
                validator = edit.validator()
                if validator is not None and edit.text().strip():
                    state, _, _ = validator.validate(edit.text(), 0)
                else:
                    state = QValidator.Acceptable
                palette = edit.palette()
                colors = {
                    QValidator.Intermediate: (Qt.yellow, Qt.black),
                    QValidator.Invalid: (Qt.red, Qt.black),
                }.get(state, None)
                if colors is None:
                    palette = QPalette()
                else:
                    palette.setColor(QPalette.Base, colors[0])
                    palette.setColor(QPalette.Text, colors[1])

                cr = edit.cursorRect()
                p = edit.mapToGlobal(cr.bottomRight())
                edit.setPalette(palette)
                if state != QValidator.Acceptable and edit.isVisible():
                    validator.show_tip(edit, p, edit.toolTip(),
                                       textFormat=Qt.RichText)
                else:
                    validator.show_tip(edit, p, "")
            return edit, edit.textChanged

        children = []

        def button(id_, *controls, stretch=True):
            layout = QHBoxLayout()
            desc = Options[id_]
            button = QRadioButton(desc.label)
            button.setToolTip(desc.tooltip)
            self.button_group.addButton(button, id_)
            layout.addWidget(button)
            if controls:
                if stretch:
                    layout.addStretch(1)
                for c, signal in controls:
                    layout.addWidget(c)
                    if signal is not None:
                        @signal.connect
                        def arg_changed():
                            self.button_group.button(id_).setChecked(True)
                            self.update_hints(id_)

            children.append(layout)
            button_box.layout().addLayout(layout)
            return (*controls, (None, ))[0][0]

        button_box = gui.vBox(box)
        button_box.layout().setSpacing(0)
        button_box.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred))
        self.button_group = QButtonGroup(self)
        self.button_group.idClicked.connect(self.update_hints)

        button(Methods.Keep)
        button(Methods.Remove)

        self.binning_spin = button(Methods.Binning, intspin())
        validator = QDoubleValidator()
        validator.setBottom(0)
        self.width_line = button(Methods.FixedWidth, widthline(validator))

        self.width_time_unit = u = QComboBox(self)
        u.setContentsMargins(0, 0, 0, 0)
        u.addItems([unit + "(s)" for unit in time_units])
        validator = QIntValidator()
        validator.setBottom(1)
        self.width_time_line = button(Methods.FixedWidthTime,
                                      widthline(validator),
                                      (u, u.currentTextChanged))

        self.freq_spin = button(Methods.EqualFreq, intspin())
        self.width_spin = button(Methods.EqualWidth, intspin())
        button(Methods.MDL)

        self.copy_to_custom = FixedSizeButton(
            text="CC", toolTip="Copy the current cut points to manual mode")
        self.copy_to_custom.clicked.connect(self._copy_to_manual)
        self.threshold_line = button(Methods.Custom,
                                     manual_cut_editline(),
                                     (self.copy_to_custom, None),
                                     stretch=False)
        button(Methods.Default)
        maxheight = max(w.sizeHint().height() for w in children)
        for w in children:
            w.itemAt(0).widget().setFixedHeight(maxheight)
        button_box.layout().addStretch(1)

    def _update_default_model(self):
        """Update data in the model showing default settings"""
        model = self.varview.default_view.model()
        model.setData(model.index(0), self.var_hints[DefaultKey], Qt.UserRole)

    def _set_mdl_button(self):
        """Disable MDL discretization for data with non-discrete class"""
        mdl_button = self.button_group.button(Methods.MDL)
        if self.data is None or self.data.domain.has_discrete_class:
            mdl_button.setEnabled(True)
        else:
            if mdl_button.isChecked():
                self._check_button(Methods.Keep, True)
            mdl_button.setEnabled(False)

    def _check_button(self, method_id: Methods, checked: bool):
        """Checks the given button"""
        self.button_group.button(method_id).setChecked(checked)

    def _uncheck_all_buttons(self):
        """Uncheck all radio buttons"""
        group = self.button_group
        button = group.checkedButton()
        if button is not None:
            group.setExclusive(False)
            button.setChecked(False)
            group.setExclusive(True)

    def _set_radio_enabled(self, method_id: Methods, value: bool):
        """Enable/disable radio button and related controls"""
        if self.button_group.button(method_id).isChecked() and not value:
            self._uncheck_all_buttons()
        self.button_group.button(method_id).setEnabled(value)
        for control_name in Options[method_id].controls:
            getattr(self, control_name).setEnabled(value)

    def _get_values(self, method_id: Methods) -> Tuple[Union[int, float, str]]:
        """Return parameters from controls pertaining to the given method"""
        controls = Options[method_id].controls
        values = []
        for control_name in controls:
            control = getattr(self, control_name)
            if isinstance(control, QSpinBox):
                values.append(control.value())
            elif isinstance(control, QComboBox):
                values.append(control.currentIndex())
            else:
                values.append(control.text())
        return tuple(values)

    def _set_values(self, method_id: Methods,
                    values: Tuple[Union[str, int, float]]):
        """
        Set controls pertaining to the given method to parameters from hint
        """
        controls = Options[method_id].controls
        for control_name, value in zip(controls, values):
            control = getattr(self, control_name)
            if isinstance(control, QSpinBox):
                control.setValue(value)
            elif isinstance(control, QComboBox):
                control.setCurrentIndex(value)
            else:
                control.setText(value)

    def varkeys_for_selection(self) -> List[KeyType]:
        """
        Return list of KeyType's for selected variables (for indexing var_hints)

        If 'Default settings' are selected, this returns DefaultKey
        """
        model = self.varview.model()
        varkeys = [variable_key(model[index.row()])
                   for index in self.varview.selectionModel().selectedRows()]
        return varkeys or [DefaultKey]  # default settings are selected

    def update_hints(self, method_id: Methods):
        """
        Callback for radio buttons and for controls regulating parameters

        This function:
        - updates `var_hints` for all selected methods
        - invalidates (removes) `discretized_vars` for affected variables
        - calls _update_discretizations to compute and commit new discretization
        - calls deferred commit

        Data for list view models is updated in _update_discretizations
        """
        if self.__interface_update:
            return

        method_id = Methods(method_id)
        args = self._get_values(method_id)
        keys = self.varkeys_for_selection()
        if method_id == Methods.Default:
            for key in keys:
                if key in self.var_hints:
                    del self.var_hints[key]
        else:
            self.var_hints.update(dict.fromkeys(keys, VarHint(method_id, args)))
        if keys == [DefaultKey]:
            invalidate = set(self.discretized_vars) - set(self.var_hints)
        else:
            invalidate = keys
        for key in invalidate:
            del self.discretized_vars[key]

        if keys == [DefaultKey]:
            self._update_default_model()
        self._update_discretizations()
        self.commit.deferred()

    def _update_discretizations(self):
        """
        Compute invalidated (missing) discretizations

        Also set data for list view models for all invalidated variables
        """
        if self.data is None:
            return

        default_hint = self.var_hints[DefaultKey]
        model = self.varview.model()
        for index, var in enumerate(model):
            key = variable_key(var)
            if key in self.discretized_vars:
                continue  # still valid
            var_hint = self.var_hints.get(key)
            points, dvar = self._discretize_var(var, var_hint or default_hint)
            self.discretized_vars[key] = dvar
            values = getattr(dvar, "values", ())
            model.setData(model.index(index),
                          DiscDesc(var_hint, points, values),
                          Qt.UserRole)

    def _discretize_var(self, var: ContinuousVariable, hint: VarHint) \
        -> Tuple[str, Optional[Variable]]:
        """
        Discretize using method and data in the hint.

        Returns a description (list of points or error/warning) and a
        - discrete variable
        - same variable (if kept numeric)
        - None (if removed or errored)
        """
        if isinstance(var, TimeVariable):
            if hint.method_id in (Methods.FixedWidth, Methods.Custom):
                return ": <keep, time var>", var
        else:
            if hint.method_id == Methods.FixedWidthTime:
                return ": <keep, not time>", var

        function = Options[hint.method_id].function
        dvar = function(self.data, var, *hint.args)
        if isinstance(dvar, str):
            return f" <{dvar}>", None  # error
        if dvar is None:
            return "", None  # removed
        elif dvar is var:
            return "", var  # no transformation
        thresholds = dvar.compute_value.points
        if len(thresholds) == 0:
            return " <removed>", None
        return ": " + ", ".join(map(var.repr_val, thresholds)), dvar

    def _copy_to_manual(self):
        """
        Callback for 'CC' button

        Sets selected variables' method to "Custom" and copies thresholds
        to their VarHints. Variables that are not discretized (for any reason)
        are skipped.

        Discretizations are invalidated and then updated
        (`_update_discretizations`).

        If all selected variables have the same thresholds, it copies it to
        the line edit. Otherwise it unchecks all radio buttons to keep the
        interface consistent.
        """
        varkeys = self.varkeys_for_selection()
        texts = set()
        for key in varkeys:
            dvar = self.discretized_vars.get(key)
            fmt = self.data.domain[key[0]].repr_val
            if isinstance(dvar, DiscreteVariable):
                text = ", ".join(map(fmt, dvar.compute_value.points))
                texts.add(text)
                self.var_hints[key] = VarHint(Methods.Custom, (text, ))
                del self.discretized_vars[key]
        try:
            self.__interface_update = True
            if len(texts) == 1:
                self.threshold_line.setText(texts.pop())
            else:
                self._uncheck_all_buttons()
        finally:
            self.__interface_update = False
        self._update_discretizations()
        self.commit.deferred()

    def _default_selected(self, selected):
        """Callback for selecting 'Default setting'"""
        if not selected:
            # Prevent infinite recursion (with _var_selection_changed)
            return
        self.varview.selectionModel().clearSelection()
        self._update_interface()

        set_enabled = self._set_radio_enabled
        set_enabled(Methods.Default, False)
        set_enabled(Methods.FixedWidth, True)
        set_enabled(Methods.FixedWidthTime, True)
        set_enabled(Methods.Custom, True)

    def _var_selection_changed(self, _):
        """Callback for changed selection in listview with variables"""
        selected = self.varview.selectionModel().selectedIndexes()
        if not selected:
            # Prevent infinite recursion (with _default_selected)
            return
        self.varview.default_view.selectionModel().clearSelection()
        self._update_interface()

        set_enabled = self._set_radio_enabled
        vars_ = [self.data.domain[name]
                 for name, _ in self.varkeys_for_selection()]
        no_time = not any(isinstance(var, TimeVariable) for var in vars_)
        all_time = all(isinstance(var, TimeVariable) for var in vars_)
        set_enabled(Methods.Default, True)
        set_enabled(Methods.FixedWidth, no_time)
        set_enabled(Methods.Custom, no_time)
        set_enabled(Methods.FixedWidthTime, all_time)

    def _update_interface(self):
        """
        Update the user interface according to selection

        - If VarHints for all selected variables are the same, check the
          corresponding radio button and fill the corresponding controls;
        - otherwise, uncheck all radios.
        """
        if self.__interface_update:
            return

        try:
            self.__interface_update = True
            keys = self.varkeys_for_selection()
            mset = list(unique_everseen(map(self.var_hints.get, keys)))
            if len(mset) != 1:
                self._uncheck_all_buttons()
                return

            if mset == [None]:
                method_id, args = Methods.Default, ()
            else:
                method_id, args = mset.pop()
            self._check_button(method_id, True)
            self._set_values(method_id, args)
        finally:
            self.__interface_update = False

    @Inputs.data
    def set_data(self, data: Optional[Table]):
        self.discretized_vars = {}
        self.data = data
        self.varview.model().set_domain(None if data is None else data.domain)
        self._update_discretizations()
        self._update_default_model()
        self._set_mdl_button()
        self.commit.now()

    @gui.deferred
    def commit(self):
        if self.data is None:
            self.Outputs.data.send(None)
            return

        def part(variables: List[Variable]) -> List[Variable]:
            return [dvar
                    for dvar in (self.discretized_vars.get(variable_key(v), v)
                                 for v in variables)
                    if dvar]

        d = self.data.domain
        domain = Domain(part(d.attributes), part(d.class_vars), part(d.metas))
        output = self.data.transform(domain)
        self.Outputs.data.send(output)

    def send_report(self):
        dmodel = self.varview.default_view.model()
        desc = dmodel.data(dmodel.index(0))
        self.report_items((tuple(desc.split(": ", maxsplit=1)), ))
        model = self.varview.model()
        reported = []
        for row in range(model.rowCount()):
            name = model[row].name
            desc = model.data(model.index(row), Qt.UserRole)
            if desc.hint is not None:
                name = f"{name} ({format_desc(desc.hint)})"
            reported.append((name, ', '.join(desc.values)))
        self.report_items("Variables", reported)

    @classmethod
    def migrate_settings(cls, settings, version):
        if version is None or version < 2:
            # was stored as int indexing Methods (but offset by 1)
            default = settings.pop("default_method", 0)
            default = Methods(default + 1)
            settings["default_method_name"] = default.name

        if version is None or version < 3:
            method_name = settings.pop("default_method_name",
                                       DefaultHint.method_id.name)
            k = settings.pop("default_k", 3)
            cut_points = settings.pop("default_cutpoints", ())

            method_id = getattr(Methods, method_name)
            if method_id in (Methods.EqualFreq, Methods.EqualWidth):
                args = (k, )
            elif method_id == Methods.Custom:
                args = (cut_points, )
            else:
                args = ()
            default_hint = VarHint(method_id, args)
            var_hints = {DefaultKey: default_hint}
            for context in settings.pop("context_settings", []):
                values = context.values
                if "saved_var_states" not in values:
                    continue
                var_states, _ = values.pop("saved_var_states")
                for (tpe, name), dstate in var_states.items():
                    key = (name, tpe == 4)  # time variable == 4
                    method = dstate.method
                    method_name = type(method).__name__.replace("Leave", "Keep")
                    if method_name == "Default":
                        continue
                    if method_name == "Custom":
                        args = (", ".join(f"{x:g}" for x in method.points), )
                    else:
                        args = tuple(method)
                    var_hints[key] = VarHint(getattr(Methods, method_name), args)
            settings["var_hints"] = var_hints


if __name__ == "__main__":  # pragma: no cover
    #WidgetPreview(OWDiscretize).run(Table("/Users/janez/Downloads/banking-crises.tab"))
    WidgetPreview(OWDiscretize).run(Table("heart_disease"))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user