Пример #1
0
    def zoom_actions(self, parent):
        def zoom(s):
            """
            Zoom in/out by factor `s`.
            scaleBy scales the view's bounds (the axis range)
            """
            self.view_box.scaleBy((1 / s, 1 / s))

        def fit_to_view():
            self.viewbox.autoRange()

        zoom_in = QAction(
            "Zoom in", parent, triggered=lambda: zoom(1.25)
        )
        zoom_in.setShortcuts([QKeySequence(QKeySequence.ZoomIn),
                              QKeySequence(parent.tr("Ctrl+="))])
        zoom_out = QAction(
            "Zoom out", parent, shortcut=QKeySequence.ZoomOut,
            triggered=lambda: zoom(1 / 1.25)
        )
        zoom_fit = QAction(
            "Fit in view", parent,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
            triggered=fit_to_view
        )
        parent.addActions([zoom_in, zoom_out, zoom_fit])
Пример #2
0
    def __init__(self, *args):
        super().__init__(*args)
        self.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        self.__backgroundIcon = QIcon()

        self.__autoScroll = False
        self.__autoScrollMargin = 16
        self.__autoScrollTimer = QTimer(self)
        self.__autoScrollTimer.timeout.connect(self.__autoScrollAdvance)

        # scale factor accumulating partial increments from wheel events
        self.__zoomLevel = 100
        # effective scale level(rounded to whole integers)
        self.__effectiveZoomLevel = 100

        self.__zoomInAction = QAction(
            self.tr("Zoom in"), self, objectName="action-zoom-in",
            shortcut=QKeySequence.ZoomIn,
            triggered=self.zoomIn,
        )

        self.__zoomOutAction = QAction(
            self.tr("Zoom out"), self, objectName="action-zoom-out",
            shortcut=QKeySequence.ZoomOut,
            triggered=self.zoomOut
        )
        self.__zoomResetAction = QAction(
            self.tr("Reset Zoom"), self, objectName="action-zoom-reset",
            triggered=self.zoomReset,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0)
        )
Пример #3
0
 def createActionForItem(self, item):
     """Create the QAction instance for item.
     """
     action = QAction(item.icon(), item.text(), self,
                      toolTip=item.toolTip())
     action.setData(item)
     return action
Пример #4
0
 def createActionForItem(self, index):
     """Create the QAction instance for item at `index` (`QModelIndex`).
     """
     action = QAction(
         item_icon(index), item_text(index), self,
         toolTip=item_tooltip(index)
     )
     action.setData(QPersistentModelIndex(index))
     return action
Пример #5
0
    def _setup_gui_labels(self):
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.setSpacing(1)

        self.labels_edit = QTreeView()
        self.labels_edit.setEditTriggers(QTreeView.CurrentChanged)
        self.labels_edit.setRootIsDecorated(False)

        self.labels_model = DictItemsModel()
        self.labels_edit.setModel(self.labels_model)

        self.labels_edit.selectionModel().selectionChanged.connect(
            self.on_label_selection_changed)

        # Necessary signals to know when the labels change
        self.labels_model.dataChanged.connect(self.on_labels_changed)
        self.labels_model.rowsInserted.connect(self.on_labels_changed)
        self.labels_model.rowsRemoved.connect(self.on_labels_changed)

        vlayout.addWidget(self.labels_edit)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        hlayout.setSpacing(1)
        self.add_label_action = QAction(
            "+", self,
            toolTip="Add a new label.",
            triggered=self.on_add_label,
            enabled=False,
            shortcut=QKeySequence(QKeySequence.New))

        self.remove_label_action = QAction(
            unicodedata.lookup("MINUS SIGN"), self,
            toolTip="Remove selected label.",
            triggered=self.on_remove_label,
            enabled=False,
            shortcut=QKeySequence(QKeySequence.Delete))

        button_size = gui.toolButtonSizeHint()
        button_size = QSize(button_size, button_size)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.add_label_action)
        hlayout.addWidget(button)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.remove_label_action)
        hlayout.addWidget(button)
        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        self.main_form.addRow("Labels:", vlayout)
Пример #6
0
    def __insertItem(self, index, item):
        """
        Insert a widget action (from a `QStandardItem`) at index.
        """
        value = item.data(self.__actionRole)
        if value is not None:
            action = value
        else:
            action = QAction(item.text(), self)
            action.setIcon(item.icon())

        self.insertAction(index, action)
Пример #7
0
    def __insertItem(self, index, item):
        """
        Insert a widget action from `item` (`QModelIndex`) at `index`.
        """
        value = item.data(self.__actionRole)
        if isinstance(value, QAction):
            action = value
        else:
            action = QAction(item_text(item), self)
            action.setIcon(item_icon(item))
            action.setToolTip(item_tooltip(item))

        self.insertAction(index, action)
Пример #8
0
    def __init__(self, plot, icon_name=None, attr_name='', attr_value=None, callback=None, parent=None):
        QAction.__init__(self, parent)

        if type(callback) == str:
            callback = getattr(plot, callback, None)
        if callback:
            self.triggered.connect(callback)
        if attr_name:
            self._plot = plot
            self.attr_name = attr_name
            self.attr_value = attr_value
            self.triggered.connect(self.set_attribute)
        if icon_name:
            self.setIcon(
                QIcon(os.path.join(os.path.dirname(__file__),
                      "../../icons", icon_name + '.png')))
            self.setIconVisibleInMenu(True)
Пример #9
0
    def createTabButton(self, widget, text, icon=None, toolTip=None):
        """
        Create the tab button for `widget`.
        """
        action = QAction(text, self)
        action.setCheckable(True)

        if icon:
            action.setIcon(icon)

        if toolTip:
            action.setToolTip(toolTip)
        self.__tabActionGroup.addAction(action)
        self.__actionMapper.setMapping(action, action)
        action.toggled.connect(self.__actionMapper.map)

        button = ToolBoxTabButton(self, objectName="toolbox-tab-button")
        button.setDefaultAction(action)
        button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        button.setSizePolicy(QSizePolicy.Ignored,
                             QSizePolicy.Fixed)

        if self.__tabIconSize.isValid():
            button.setIconSize(self.__tabIconSize)

        if self.__tabButtonHeight > 0:
            button.setFixedHeight(self.__tabButtonHeight)

        return button
Пример #10
0
 def add_zoom_actions(self, menu):
     zoom_in = QAction(
         "Zoom in", self, triggered=self.plot.vb.set_mode_zooming
     )
     zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)])
     zoom_in.setShortcutContext(Qt.WidgetWithChildrenShortcut)
     self.addAction(zoom_in)
     if menu:
         menu.addAction(zoom_in)
     zoom_fit = QAction(
         "Zoom to fit", self,
         triggered=lambda x: (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning())
     )
     zoom_fit.setShortcuts([Qt.Key_Backspace, QKeySequence(Qt.ControlModifier | Qt.Key_0)])
     zoom_fit.setShortcutContext(Qt.WidgetWithChildrenShortcut)
     self.addAction(zoom_fit)
     if menu:
         menu.addAction(zoom_fit)
Пример #11
0
 def __init__(self, parent=None):
     QObject.__init__(self, parent)
     self.__splitter = None
     self.__widget = None
     self.__updateOnShow = True  # Need __update on next show event
     self.__animationEnabled = True
     self.__size = -1
     self.__expanded = False
     self.__animation = QPropertyAnimation(self, b"size_", self, duration=200)
     self.__action = QAction("toogle-expanded", self, checkable=True)
     self.__action.triggered[bool].connect(self.setExpanded)
Пример #12
0
 def __init__(self, parent, plot):
     super().__init__(parent, plot)
     self._item = None
     self._start_pos = None
     self._selection_rect = None
     self._mouse_dragging = False
     self._delete_action = QAction(
         "Delete", self, shortcutContext=Qt.WindowShortcut
     )
     self._delete_action.setShortcuts([QKeySequence.Delete,
                                       QKeySequence("Backspace")])
     self._delete_action.triggered.connect(self.delete)
Пример #13
0
    def test_lineedit(self):
        """test LineEdit
        """
        line = LineEdit()
        line.show()

        action1 = QAction(QIcon(line.style().standardPixmap(
                                    QStyle.SP_ArrowBack)
                                ),
                          "Search", line)
        menu = QMenu()
        menu.addAction("Regex")
        menu.addAction("Wildcard")
        action1.setMenu(menu)

        line.setAction(action1, LineEdit.LeftPosition)
        self.assertIs(line.actionAt(LineEdit.LeftPosition), action1)
        self.assertTrue(line.button(LineEdit.LeftPosition) is not None)
        self.assertTrue(line.button(LineEdit.RightPosition) is None)

        with self.assertRaises(ValueError):
            line.removeActionAt(100)

        line.removeActionAt(LineEdit.LeftPosition)
        self.assertIs(line.actionAt(LineEdit.LeftPosition), None)

        line.setAction(action1, LineEdit.LeftPosition)

        action2 = QAction(QIcon(line.style().standardPixmap(
                                        QStyle.SP_TitleBarCloseButton)),
                          "Delete", line)
        line.setAction(action2, LineEdit.RightPosition)

        line.setPlaceholderText("Search")
        self.assertEqual(line.placeholderText(), "Search")

        b = line.button(LineEdit.RightPosition)
        b.setFlat(False)
        self.app.exec_()
Пример #14
0
    def addTab(self, widget, text, toolTip=None, icon=None):
        if self.__macUnified:
            action = QAction(text, self)

            if toolTip:
                action.setToolTip(toolTip)

            if icon:
                action.setIcon(toolTip)
            action.setData(len(self.tab.actions()))

            self.tab.addAction(action)

            self.stack.addWidget(widget)
        else:
            i = self.tab.addTab(widget, text)

            if toolTip:
                self.tab.setTabToolTip(i, toolTip)

            if icon:
                self.tab.setTabIcon(i, icon)
Пример #15
0
        def __init__(self, parent=None, widget=None, title=None, **kwargs):

            super().__init__(parent, **kwargs)
            self.setFeatures(QDockWidget.DockWidgetClosable)
            self.setAllowedAreas(Qt.NoDockWidgetArea)

            self.__title = ""
            self.__icon = ""
            self.__focusframe = None

            self.__deleteaction = QAction(
                "Remove", self, shortcut=QKeySequence.Delete,
                enabled=False, triggered=self.closeRequested
            )
            self.addAction(self.__deleteaction)

            if widget is not None:
                self.setWidget(widget)
            self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

            if title:
                self.setTitle(title)

            self.setFocusPolicy(Qt.ClickFocus | Qt.TabFocus)
Пример #16
0
    def create_action_for_item(self, item):
        """
        Create a QAction instance for the widget description item.
        """
        name = item.text()
        tooltip = item.toolTip()
        whatsThis = item.whatsThis()
        icon = item.icon()
        if icon:
            action = QAction(icon, name, self, toolTip=tooltip,
                             whatsThis=whatsThis,
                             statusTip=name)
        else:
            action = QAction(name, self, toolTip=tooltip,
                             whatsThis=whatsThis,
                             statusTip=name)

        widget_desc = item.data(self.WIDGET_DESC_ROLE)
        action.setData(widget_desc)
        action.setProperty("item", item)
        return action
Пример #17
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        form = self.layout().itemAt(0)
        assert isinstance(form, QFormLayout)
        self.ordered_cb = QCheckBox("Ordered",
                                    self,
                                    toolTip="Is this an ordered categorical.")
        self.ordered_cb.toggled.connect(self._set_ordered)
        #: A list model of discrete variable's values.
        self.values_model = itemmodels.PyListModel(
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        vlayout = QVBoxLayout(spacing=1, margin=0)
        self.values_edit = QListView(editTriggers=QListView.DoubleClicked
                                     | QListView.EditKeyPressed)
        self.values_edit.setItemDelegate(CategoriesEditDelegate(self))
        self.values_edit.setModel(self.values_model)
        self.values_model.dataChanged.connect(self.on_values_changed)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)
        self.values_model.layoutChanged.connect(
            self.on_value_selection_changed)
        self.values_model.rowsMoved.connect(self.on_value_selection_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout(spacing=1, margin=0)

        self.categories_action_group = group = QActionGroup(
            self, objectName="action-group-categories", enabled=False)
        self.move_value_up = QAction(
            "\N{UPWARDS ARROW}",
            group,
            toolTip="Move the selected item up.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier
                                  | Qt.Key_BracketLeft),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_up.triggered.connect(self.move_up)

        self.move_value_down = QAction(
            "\N{DOWNWARDS ARROW}",
            group,
            toolTip="Move the selected item down.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier
                                  | Qt.Key_BracketRight),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_down.triggered.connect(self.move_down)

        self.add_new_item = QAction(
            "+",
            group,
            objectName="action-add-item",
            toolTip="Append a new item.",
            shortcut=QKeySequence(QKeySequence.New),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.remove_item = QAction(
            "\N{MINUS SIGN}",
            group,
            objectName="action-remove-item",
            toolTip="Delete the selected item.",
            shortcut=QKeySequence(QKeySequence.Delete),
            shortcutContext=Qt.WidgetShortcut,
        )

        self.add_new_item.triggered.connect(self._add_category)
        self.remove_item.triggered.connect(self._remove_category)

        button1 = FixedSizeButton(self,
                                  defaultAction=self.move_value_up,
                                  accessibleName="Move up")
        button2 = FixedSizeButton(self,
                                  defaultAction=self.move_value_down,
                                  accessibleName="Move down")
        button3 = FixedSizeButton(self,
                                  defaultAction=self.add_new_item,
                                  accessibleName="Add")
        button4 = FixedSizeButton(self,
                                  defaultAction=self.remove_item,
                                  accessibleName="Remove")
        self.values_edit.addActions([
            self.move_value_up, self.move_value_down, self.add_new_item,
            self.remove_item
        ])
        hlayout.addWidget(button1)
        hlayout.addWidget(button2)
        hlayout.addSpacing(3)
        hlayout.addWidget(button3)
        hlayout.addWidget(button4)

        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        form.insertRow(1, "", self.ordered_cb)
        form.insertRow(2, "Values:", vlayout)

        QWidget.setTabOrder(self.name_edit, self.ordered_cb)
        QWidget.setTabOrder(self.ordered_cb, self.values_edit)

        QWidget.setTabOrder(self.values_edit, button1)
        QWidget.setTabOrder(button1, button2)
        QWidget.setTabOrder(button2, button3)
        QWidget.setTabOrder(button3, button4)
Пример #18
0
    def test_tool_grid(self):
        w = ToolGrid()

        w.show()
        self.app.processEvents()

        def buttonsOrderedVisual():
            # Process layout events so the buttons have right positions
            self.app.processEvents()
            buttons = w.findChildren(QToolButton)
            return list(sorted(buttons, key=lambda b: (b.y(), b.x())))

        def buttonsOrderedLogical():
            return list(map(w.buttonForAction, w.actions()))

        def assertOrdered():
            self.assertSequenceEqual(buttonsOrderedLogical(),
                                     buttonsOrderedVisual())

        action_a = QAction("A", w)
        action_b = QAction("B", w)
        action_c = QAction("C", w)
        action_d = QAction("D", w)

        w.addAction(action_b)
        w.insertAction(0, action_a)
        self.assertSequenceEqual(w.actions(), [action_a, action_b])
        assertOrdered()

        w.addAction(action_d)
        w.insertAction(action_d, action_c)

        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_c, action_d])
        assertOrdered()

        w.removeAction(action_c)
        self.assertSequenceEqual(w.actions(), [action_a, action_b, action_d])

        assertOrdered()

        w.removeAction(action_a)
        self.assertSequenceEqual(w.actions(), [action_b, action_d])

        assertOrdered()

        w.insertAction(0, action_a)
        self.assertSequenceEqual(w.actions(), [action_a, action_b, action_d])

        assertOrdered()

        w.setColumnCount(2)
        self.assertSequenceEqual(w.actions(), [action_a, action_b, action_d])

        assertOrdered()

        w.insertAction(2, action_c)
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_c, action_d])
        assertOrdered()

        w.clear()
        # test no 'before' action edge case
        w.insertAction(0, action_a)
        self.assertIs(action_a, w.actions()[0])
        w.insertAction(1, action_b)
        self.assertSequenceEqual(w.actions(), [action_a, action_b])

        w.clear()
        w.setActions([action_a, action_b, action_c, action_d])
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_c, action_d])
        assertOrdered()

        triggered_actions = []

        def p(action):
            print(action.text())

        w.actionTriggered.connect(p)
        w.actionTriggered.connect(triggered_actions.append)
        action_a.trigger()

        w.show()
        self.qWait()
Пример #19
0
    def __init__(self):
        super().__init__()

        self.in_data = None
        self.in_distance = None
        self.in_learner = None
        self.in_classifier = None
        self.in_object = None
        self.auto_execute = False

        for s in self.libraryListSource:
            s.flags = 0

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " + \
            "<li>".join(t.name for t in self.inputs) + \
            "</ul></p><p>Output variables:<ul><li>" + \
            "<li>".join(t.name for t in self.outputs) + \
            "</ul></p>"
        )

        self.libraryList = itemmodels.PyListModel(
            [], self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked |
                         QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored,
                                   QSizePolicy.Preferred)
        )
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged
        )
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.auto_commit(
            self.controlArea, self, "auto_execute", "Execute",
            auto_label="Auto Execute")

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python Script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)

        select_row(self.libraryView, self.currentScriptIndex)

        self.splitCanvas.setSizes([2, 1])
        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

        self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
        self.controlArea.layout().addStretch(1)
        self.resize(800, 600)
Пример #20
0
    class Frame(QDockWidget):
        """
        Widget frame with a handle.
        """
        closeRequested = Signal()

        def __init__(self, parent=None, widget=None, title=None, **kwargs):

            super().__init__(parent, **kwargs)
            self.setFeatures(QDockWidget.DockWidgetClosable)
            self.setAllowedAreas(Qt.NoDockWidgetArea)

            self.__title = ""
            self.__icon = ""
            self.__focusframe = None

            self.__deleteaction = QAction(
                "Remove", self, shortcut=QKeySequence.Delete,
                enabled=False, triggered=self.closeRequested
            )
            self.addAction(self.__deleteaction)

            if widget is not None:
                self.setWidget(widget)
            self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

            if title:
                self.setTitle(title)

            self.setFocusPolicy(Qt.ClickFocus | Qt.TabFocus)

        def setTitle(self, title):
            if self.__title != title:
                self.__title = title
                self.setWindowTitle(title)
                self.update()

        def setIcon(self, icon):
            icon = QIcon(icon)
            if self.__icon != icon:
                self.__icon = icon
                self.setWindowIcon(icon)
                self.update()

        def paintEvent(self, event):
            super().paintEvent(event)
            painter = QStylePainter(self)
            opt = QStyleOptionFrame()
            opt.initFrom(self)
            painter.drawPrimitive(QStyle.PE_FrameDockWidget, opt)
            painter.end()

        def focusInEvent(self, event):
            event.accept()
            self.__focusframe = QFocusFrame(self)
            self.__focusframe.setWidget(self)
            self.__deleteaction.setEnabled(True)

        def focusOutEvent(self, event):
            event.accept()
            if self.__focusframe is not None:
                self.__focusframe.deleteLater()
                self.__focusframe = None
            self.__deleteaction.setEnabled(False)

        def closeEvent(self, event):
            super().closeEvent(event)
            event.ignore()
            self.closeRequested.emit()
Пример #21
0
    def __init__(self):
        super().__init__()
        #: widget's runtime state
        self.__state = State.NoState
        self.data = None
        self._n_image_categories = 0
        self._n_image_data = 0
        self._n_skipped = 0

        self.__invalidated = False
        self.__pendingTask = None

        vbox = gui.vBox(self.controlArea)
        hbox = gui.hBox(vbox)
        self.recent_cb = QComboBox(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=16,
            acceptDrops=True
        )
        self.recent_cb.installEventFilter(self)
        self.recent_cb.activated[int].connect(self.__onRecentActivated)
        icons = standard_icons(self)

        browseaction = QAction(
            "Open/Load Images", self,
            iconText="\N{HORIZONTAL ELLIPSIS}",
            icon=icons.dir_open_icon,
            toolTip="Select a directory from which to load the images"
        )
        browseaction.triggered.connect(self.__runOpenDialog)
        reloadaction = QAction(
            "Reload", self,
            icon=icons.reload_icon,
            toolTip="Reload current image set"
        )
        reloadaction.triggered.connect(self.reload)
        self.__actions = namespace(
            browse=browseaction,
            reload=reloadaction,
        )

        browsebutton = QPushButton(
            browseaction.iconText(),
            icon=browseaction.icon(),
            toolTip=browseaction.toolTip(),
            clicked=browseaction.trigger
        )
        reloadbutton = QPushButton(
            reloadaction.iconText(),
            icon=reloadaction.icon(),
            clicked=reloadaction.trigger,
            default=True,
        )

        hbox.layout().addWidget(self.recent_cb)
        hbox.layout().addWidget(browsebutton)
        hbox.layout().addWidget(reloadbutton)

        self.addActions([browseaction, reloadaction])

        reloadaction.changed.connect(
            lambda: reloadbutton.setEnabled(reloadaction.isEnabled())
        )
        box = gui.vBox(vbox, "Info")
        self.infostack = QStackedWidget()

        self.info_area = QLabel(
            text="No image set selected",
            wordWrap=True
        )
        self.progress_widget = QProgressBar(
            minimum=0, maximum=0
        )
        self.cancel_button = QPushButton(
            "Cancel", icon=icons.cancel_icon,
        )
        self.cancel_button.clicked.connect(self.cancel)

        w = QWidget()
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)

        hlayout.addWidget(self.progress_widget)
        hlayout.addWidget(self.cancel_button)
        vlayout.addLayout(hlayout)

        self.pathlabel = TextLabel()
        self.pathlabel.setTextElideMode(Qt.ElideMiddle)
        self.pathlabel.setAttribute(Qt.WA_MacSmallSize)

        vlayout.addWidget(self.pathlabel)
        w.setLayout(vlayout)

        self.infostack.addWidget(self.info_area)
        self.infostack.addWidget(w)

        box.layout().addWidget(self.infostack)

        self.__initRecentItemsModel()
        self.__invalidated = True
        self.__executor = ThreadExecutor(self)

        QApplication.postEvent(self, QEvent(RuntimeEvent.Init))
Пример #22
0
 def __setupUi(self):
     icon = icon_loader().get("icons/Search.svg")
     action = QAction(icon, "Search", self)
     self.setAction(action, LineEdit.LeftPosition)
Пример #23
0
class SplitterResizer(QObject):
    """
    An object able to control the size of a widget in a QSplitter instance.
    """
    def __init__(self, parent=None, **kwargs):
        # type: (Optional[QObject], Any) -> None
        super().__init__(parent, **kwargs)
        self.__splitter = None  # type: Optional[QSplitter]
        self.__widget = None  # type: Optional[QWidget]
        self.__updateOnShow = True  # Need __update on next show event
        self.__animationEnabled = True
        self.__size = -1
        self.__expanded = False
        self.__animation = QPropertyAnimation(self,
                                              b"size_",
                                              self,
                                              duration=200)
        self.__action = QAction("toggle-expanded", self, checkable=True)
        self.__action.triggered[bool].connect(self.setExpanded)

    def setSize(self, size):
        # type: (int) -> None
        """
        Set the size of the controlled widget (either width or height
        depending on the orientation).

        .. note::
            The controlled widget's size is only updated when it it is shown.
        """
        if self.__size != size:
            self.__size = size
            self.__update()

    def size(self):
        # type: () -> int
        """
        Return the size of the widget in the splitter (either height of
        width) depending on the splitter orientation.
        """
        if self.__splitter and self.__widget:
            index = self.__splitter.indexOf(self.__widget)
            sizes = self.__splitter.sizes()
            return sizes[index]
        else:
            return -1

    size_ = Property(int, fget=size, fset=setSize)

    def setAnimationEnabled(self, enable):
        # type: (bool) -> None
        """Enable/disable animation."""
        self.__animation.setDuration(0 if enable else 200)

    def animationEnabled(self):
        # type: () -> bool
        return self.__animation.duration() == 0

    def setSplitterAndWidget(self, splitter, widget):
        # type: (QSplitter, QWidget) -> None
        """Set the QSplitter and QWidget instance the resizer should control.

        .. note:: the widget must be in the splitter.
        """
        if splitter and widget and not splitter.indexOf(widget) > 0:
            raise ValueError("Widget must be in a splitter.")

        if self.__widget is not None:
            self.__widget.removeEventFilter(self)
        if self.__splitter is not None:
            self.__splitter.removeEventFilter(self)

        self.__splitter = splitter
        self.__widget = widget

        if widget is not None:
            widget.installEventFilter(self)
        if splitter is not None:
            splitter.installEventFilter(self)

        self.__update()

        size = self.size()
        if self.__expanded and size == 0:
            self.open()
        elif not self.__expanded and size > 0:
            self.close()

    def toggleExpandedAction(self):
        # type: () -> QAction
        """Return a QAction that can be used to toggle expanded state.
        """
        return self.__action

    def toogleExpandedAction(self):
        warnings.warn(
            "'toogleExpandedAction is deprecated, use 'toggleExpandedAction' "
            "instead.",
            DeprecationWarning,
            stacklevel=2)
        return self.toggleExpandedAction()

    def open(self):
        # type: () -> None
        """Open the controlled widget (expand it to sizeHint).
        """
        self.__expanded = True
        self.__action.setChecked(True)

        if self.__splitter is None or self.__widget is None:
            return

        hint = self.__widget.sizeHint()

        if self.__splitter.orientation() == Qt.Vertical:
            end = hint.height()
        else:
            end = hint.width()

        self.__animation.setStartValue(0)
        self.__animation.setEndValue(end)
        self.__animation.start()

    def close(self):
        # type: () -> None
        """Close the controlled widget (shrink to size 0).
        """
        self.__expanded = False
        self.__action.setChecked(False)

        if self.__splitter is None or self.__widget is None:
            return

        self.__animation.setStartValue(self.size())
        self.__animation.setEndValue(0)
        self.__animation.start()

    def setExpanded(self, expanded):
        # type: (bool) -> None
        """Set the expanded state."""
        if self.__expanded != expanded:
            if expanded:
                self.open()
            else:
                self.close()

    def expanded(self):
        # type: () -> bool
        """Return the expanded state."""
        return self.__expanded

    def __update(self):
        # type: () -> None
        """Update the splitter sizes."""
        if self.__splitter and self.__widget:
            if sum(self.__splitter.sizes()) == 0:
                # schedule update on next show event
                self.__updateOnShow = True
                return

            splitter = self.__splitter
            index = splitter.indexOf(self.__widget)
            sizes = splitter.sizes()
            current = sizes[index]
            diff = current - self.__size
            sizes[index] = self.__size
            sizes[index - 1] = sizes[index - 1] + diff
            self.__splitter.setSizes(sizes)

    def eventFilter(self, obj, event):
        # type: (QObject, QEvent) -> bool
        if event.type() == QEvent.Resize and obj is self.__widget and \
                self.__animation.state() == QPropertyAnimation.Stopped:
            # Update the expanded state when the user opens/closes the widget
            # by dragging the splitter handle.
            assert self.__splitter is not None
            assert isinstance(event, QResizeEvent)
            if self.__splitter.orientation() == Qt.Vertical:
                size = event.size().height()
            else:
                size = event.size().width()

            if self.__expanded and size == 0:
                self.__action.setChecked(False)
                self.__expanded = False
            elif not self.__expanded and size > 0:
                self.__action.setChecked(True)
                self.__expanded = True

        if event.type() == QEvent.Show and obj is self.__splitter and \
                self.__updateOnShow:
            # Update the splitter state after receiving valid geometry
            self.__updateOnShow = False
            self.__update()
        return super().eventFilter(obj, event)
Пример #24
0
 def makeaction(text, parent, data=None, **kwargs):
     action = QAction(text, parent, **kwargs)
     if data is not None:
         action.setData(data)
     return action
Пример #25
0
    def __init__(self):
        super().__init__()

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWScatterPlotGraph(self, box, "ScatterPlot")
        box.layout().addWidget(self.graph.plot_widget)
        plot = self.graph.plot_widget

        axispen = QPen(self.palette().color(QPalette.Text))
        axis = plot.getAxis("bottom")
        axis.setPen(axispen)

        axis = plot.getAxis("left")
        axis.setPen(axispen)

        self.data = None  # Orange.data.Table
        self.subset_data = None  # Orange.data.Table
        self.data_metas_X = None  # self.data, where primitive metas are moved to X
        self.sql_data = None  # Orange.data.sql.table.SqlTable
        self.attribute_selection_list = None  # list of Orange.data.Variable
        self.__timer = QTimer(self, interval=1200)
        self.__timer.timeout.connect(self.add_data)

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              valueType=str)
        box = gui.vBox(self.controlArea, "Axis Data")
        dmod = DomainModel
        self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)

        vizrank_box = gui.hBox(box)
        gui.separator(vizrank_box, width=common_options["labelWidth"])
        self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank(
            vizrank_box, self, "Find Informative Projections", self.set_attr)

        gui.separator(box)

        gui.valueSlider(box,
                        self,
                        value='graph.jitter_size',
                        label='Jittering: ',
                        values=self.jitter_sizes,
                        callback=self.reset_graph_data,
                        labelFormat=lambda x: "None"
                        if x == 0 else ("%.1f %%" if x < 1 else "%d %%") % x)
        gui.checkBox(gui.indentedBox(box),
                     self,
                     'graph.jitter_continuous',
                     'Jitter continuous values',
                     callback=self.reset_graph_data)

        self.sampling = gui.auto_commit(self.controlArea,
                                        self,
                                        "auto_sample",
                                        "Sample",
                                        box="Sampling",
                                        callback=self.switch_sampling,
                                        commit=lambda: self.add_data(1))
        self.sampling.setVisible(False)

        g = self.graph.gui
        g.point_properties_box(self.controlArea)
        self.models = [self.xy_model] + g.points_models

        box = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([g.ShowLegend, g.ShowGridLines], box)
        gui.checkBox(box,
                     self,
                     value='graph.tooltip_shows_all',
                     label='Show all data on mouse hover')
        self.cb_class_density = gui.checkBox(box,
                                             self,
                                             value='graph.class_density',
                                             label='Show class density',
                                             callback=self.update_density)
        self.cb_reg_line = gui.checkBox(box,
                                        self,
                                        value='graph.show_reg_line',
                                        label='Show regression line',
                                        callback=self.update_regression_line)
        gui.checkBox(box,
                     self,
                     'graph.label_only_selected',
                     'Label only selected points',
                     callback=self.graph.update_labels)

        self.zoom_select_toolbar = g.zoom_select_toolbar(
            gui.vBox(self.controlArea, "Zoom/Select"),
            nomargin=True,
            buttons=[
                g.StateButtonsBegin, g.SimpleSelect, g.Pan, g.Zoom,
                g.StateButtonsEnd, g.ZoomReset
            ])
        buttons = self.zoom_select_toolbar.buttons
        buttons[g.Zoom].clicked.connect(self.graph.zoom_button_clicked)
        buttons[g.Pan].clicked.connect(self.graph.pan_button_clicked)
        buttons[g.SimpleSelect].clicked.connect(
            self.graph.select_button_clicked)
        buttons[g.ZoomReset].clicked.connect(self.graph.reset_button_clicked)
        self.controlArea.layout().addStretch(100)
        self.icons = gui.attributeIconDict

        p = self.graph.plot_widget.palette()
        self.graph.set_palette(p)

        gui.auto_commit(self.controlArea, self, "auto_send_selection",
                        "Send Selection", "Send Automatically")

        def zoom(s):
            """Zoom in/out by factor `s`."""
            viewbox = plot.getViewBox()
            # scaleBy scales the view's bounds (the axis range)
            viewbox.scaleBy((1 / s, 1 / s))

        def fit_to_view():
            viewbox = plot.getViewBox()
            viewbox.autoRange()

        zoom_in = QAction("Zoom in", self, triggered=lambda: zoom(1.25))
        zoom_in.setShortcuts([
            QKeySequence(QKeySequence.ZoomIn),
            QKeySequence(self.tr("Ctrl+="))
        ])
        zoom_out = QAction("Zoom out",
                           self,
                           shortcut=QKeySequence.ZoomOut,
                           triggered=lambda: zoom(1 / 1.25))
        zoom_fit = QAction("Fit in view",
                           self,
                           shortcut=QKeySequence(Qt.ControlModifier
                                                 | Qt.Key_0),
                           triggered=fit_to_view)
        self.addActions([zoom_in, zoom_out, zoom_fit])
Пример #26
0
    def __init__(self):
        super().__init__()

        for name in self.signal_names:
            setattr(self, name, {})

        for s in self.libraryListSource:
            s.flags = 0

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, '信息')
        gui.label(
            self.infoBox, self, "<p>执行Python脚本。</p><p>输入变量:<ul><li> " +
            "<li>".join(map("in_{0}, in_{0}s".format, self.signal_names)) +
            "</ul></p><p>输出变量:<ul><li>" +
            "<li>".join(map("out_{0}".format, self.signal_names)) +
            "</ul></p>")

        self.libraryList = itemmodels.PyListModel(
            [],
            self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, '脚本库')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred))
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged)
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("向库中添加新脚本")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("从库中删除脚本")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("保存", self)
        action.setToolTip("将编辑器中的更改的脚本保存到库中")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("更多", self, toolTip="更多操作")

        new_from_file = QAction("从文件导入脚本", self)
        save_to_file = QAction("将所选脚本保存到文件", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        auto = gui.auto_commit(self.controlArea,
                               self,
                               "auto_execute",
                               "运行",
                               checkbox_label="对新数据自动运行")
        self.execute_button, self.autobox = auto.button, auto.checkbox

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python脚本')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, '控制台')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)

        select_row(self.libraryView, self.currentScriptIndex)

        self.splitCanvas.setSizes([2, 1])
        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

        self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
        self.controlArea.layout().addStretch(1)
        self.resize(800, 600)
    def __init__(self, parent=None):
        super().__init__(parent)

        self._changedFlag = False
        self.__invalidated = False
        self.__runstate = OWKEGGPathwayBrowser.Initializing
        self.__in_setProgress = False

        self.controlArea.setMaximumWidth(250)
        box = gui.widgetBox(self.controlArea, "Info")
        self.infoLabel = gui.widgetLabel(box, "No data on input\n")

        gui.separator(self.controlArea)

        gui.checkBox(self.controlArea, self, "useReference", "From signal", box="Reference", callback=self.Update)

        gui.separator(self.controlArea)

        gui.checkBox(
            self.controlArea,
            self,
            "showOrthology",
            "Show pathways in full orthology",
            box="Orthology",
            callback=self.UpdateListView,
        )

        gui.checkBox(
            self.controlArea,
            self,
            "autoResize",
            "Resize to fit",
            box="Image",
            callback=self.UpdatePathwayViewTransform,
        )

        box = gui.widgetBox(self.controlArea, "Cache Control")

        gui.button(
            box,
            self,
            "Clear cache",
            callback=self.ClearCache,
            tooltip="Clear all locally cached KEGG data.",
            default=False,
            autoDefault=False,
        )

        gui.separator(self.controlArea)

        gui.auto_commit(self.controlArea, self, "autoCommit", "Commit")

        gui.rubber(self.controlArea)

        spliter = QSplitter(Qt.Vertical, self.mainArea)
        self.pathwayView = PathwayView(self, spliter)
        self.pathwayView.scene().selectionChanged.connect(self._onSelectionChanged)
        self.mainArea.layout().addWidget(spliter)

        self.listView = QTreeWidget(
            allColumnsShowFocus=True, selectionMode=QTreeWidget.SingleSelection, sortingEnabled=True, maximumHeight=200
        )

        spliter.addWidget(self.listView)

        self.listView.setColumnCount(4)
        self.listView.setHeaderLabels(["Pathway", "P value", "Genes", "Reference"])

        self.listView.itemSelectionChanged.connect(self.UpdatePathwayView)

        select = QAction("Select All", self, shortcut=QKeySequence.SelectAll)
        select.triggered.connect(self.selectAll)
        self.addAction(select)

        self.data = None
        self.input_genes = []
        self.tax_id = None
        self.use_attr_names = None
        self.gene_id_attribute = None
        self.gene_id_column = None

        self.ref_data = None
        self.ref_genes = []
        self.ref_tax_id = None
        self.ref_use_attr_names = None
        self.ref_gene_id_attribute = None
        self.ref_gene_id_column = None

        self.pathways = {}
        self.org = None

        self._executor = concurrent.ThreadExecutor()
        self.setEnabled(False)
        self.setBlocking(True)
        progress = concurrent.methodinvoke(self, "setProgress", (float,))

        def get_genome():
            """Return a KEGGGenome with the common org entries precached."""
            genome = kegg.KEGGGenome()

            essential = genome.essential_organisms()
            common = genome.common_organisms()
            # Remove duplicates of essential from common.
            # (essential + common list as defined here will be used in the
            # GUI.)
            common = [c for c in common if c not in essential]

            # TODO: Add option to specify additional organisms not
            # in the common list.

            keys = list(map(genome.org_code_to_entry_key, essential + common))

            genome.pre_cache(keys, progress_callback=progress)
            return (keys, genome)

        self._genomeTask = task = concurrent.Task(function=get_genome)
        task.finished.connect(self.__initialize_finish)

        self.progressBarInit()
        self.infoLabel.setText("Fetching organism definitions\n")
        self._executor.submit(task)
Пример #28
0
    def __init__(self):
        super().__init__()
        #: widget's runtime state
        self.__state = State.NoState
        self._soundMeta = []
        self._soundCategories = {}
        self._data = []

        self.__invalidated = False
        self.__pendingTask = None

        vbox = gui.vBox(self.controlArea)
        hbox = gui.hBox(vbox)
        self.recent_cb = QComboBox(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=16,
            acceptDrops=True)
        self.recent_cb.installEventFilter(self)
        self.recent_cb.activated[int].connect(self.__onRecentActivated)
        icons = standard_icons(self)

        browseaction = QAction(
            "Open/Load Sounds",
            self,
            iconText="\N{HORIZONTAL ELLIPSIS}",
            icon=icons.dir_open_icon,
            toolTip="Select a directory from which to load the sounds")
        browseaction.triggered.connect(self.__runOpenDialog)
        reloadaction = QAction("Reload",
                               self,
                               icon=icons.reload_icon,
                               toolTip="Reload current sounds set")
        reloadaction.triggered.connect(self.reload)
        self.__actions = namespace(
            browse=browseaction,
            reload=reloadaction,
        )

        browsebutton = QPushButton(browseaction.iconText(),
                                   icon=browseaction.icon(),
                                   toolTip=browseaction.toolTip(),
                                   clicked=browseaction.trigger)
        reloadbutton = QPushButton(
            reloadaction.iconText(),
            icon=reloadaction.icon(),
            clicked=reloadaction.trigger,
            default=True,
        )

        hbox.layout().addWidget(self.recent_cb)
        hbox.layout().addWidget(browsebutton)
        hbox.layout().addWidget(reloadbutton)

        self.addActions([browseaction, reloadaction])

        reloadaction.changed.connect(
            lambda: reloadbutton.setEnabled(reloadaction.isEnabled()))
        box = gui.vBox(vbox, "Info")
        self.infostack = QStackedWidget()

        self.info_area = QLabel(text="No sound set selected", wordWrap=True)
        self.progress_widget = QProgressBar(minimum=0, maximum=0)
        self.cancel_button = QPushButton(
            "Cancel",
            icon=icons.cancel_icon,
        )
        self.cancel_button.clicked.connect(self.cancel)

        w = QWidget()
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)

        hlayout.addWidget(self.progress_widget)
        hlayout.addWidget(self.cancel_button)
        vlayout.addLayout(hlayout)

        self.pathlabel = TextLabel()
        self.pathlabel.setTextElideMode(Qt.ElideMiddle)
        self.pathlabel.setAttribute(Qt.WA_MacSmallSize)

        vlayout.addWidget(self.pathlabel)
        w.setLayout(vlayout)

        self.infostack.addWidget(self.info_area)
        self.infostack.addWidget(w)

        box.layout().addWidget(self.infostack)

        self.__initRecentItemsModel()
        self.__invalidated = True
        self.__executor = ThreadExecutor(self)

        QApplication.postEvent(self, QEvent(RuntimeEvent.Init))
Пример #29
0
    def _init_ui(self):
        namesBox = gui.vBox(self.controlArea, "Names")

        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        gui.lineEdit(hbox, self, "attr1", "Variable X: ",
                     controlWidth=80, orientation=Qt.Horizontal,
                     callback=self._attr_name_changed)
        gui.separator(hbox, 21)
        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        attr2 = gui.lineEdit(hbox, self, "attr2", "Variable Y: ",
                             controlWidth=80, orientation=Qt.Horizontal,
                             callback=self._attr_name_changed)
        gui.separator(hbox)
        gui.checkBox(hbox, self, "hasAttr2", '', disables=attr2,
                     labelWidth=0,
                     callback=self.set_dimensions)
        gui.separator(namesBox)

        gui.widgetLabel(namesBox, "Labels")
        self.classValuesView = listView = QListView(
            selectionMode=QListView.SingleSelection,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored,
                                   QSizePolicy.Maximum)
        )
        listView.setModel(self.class_model)
        itemmodels.select_row(listView, 0)
        namesBox.layout().addWidget(listView)

        self.addClassLabel = QAction(
            "+", self,
            toolTip="Add new class label",
            triggered=self.add_new_class_label
        )

        self.removeClassLabel = QAction(
            unicodedata.lookup("MINUS SIGN"), self,
            toolTip="Remove selected class label",
            triggered=self.remove_selected_class_label
        )

        actionsWidget = itemmodels.ModelActionsWidget(
            [self.addClassLabel, self.removeClassLabel], self
        )
        actionsWidget.layout().addStretch(10)
        actionsWidget.layout().setSpacing(1)
        namesBox.layout().addWidget(actionsWidget)

        tBox = gui.vBox(self.controlArea, "Tools", addSpace=True)
        buttonBox = gui.hBox(tBox)
        toolsBox = gui.widgetBox(buttonBox, orientation=QGridLayout())

        self.toolActions = QActionGroup(self)
        self.toolActions.setExclusive(True)
        self.toolButtons = []

        for i, (name, tooltip, tool, icon) in enumerate(self.TOOLS):
            action = QAction(
                name, self,
                toolTip=tooltip,
                checkable=tool.checkable,
                icon=QIcon(icon),
            )
            action.triggered.connect(partial(self.set_current_tool, tool))

            button = QToolButton(
                iconSize=QSize(24, 24),
                toolButtonStyle=Qt.ToolButtonTextUnderIcon,
                sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.Fixed)
            )
            button.setDefaultAction(action)
            self.toolButtons.append((button, tool))

            toolsBox.layout().addWidget(button, i / 3, i % 3)
            self.toolActions.addAction(action)

        for column in range(3):
            toolsBox.layout().setColumnMinimumWidth(column, 10)
            toolsBox.layout().setColumnStretch(column, 1)

        undo = self.undo_stack.createUndoAction(self)
        redo = self.undo_stack.createRedoAction(self)

        undo.setShortcut(QKeySequence.Undo)
        redo.setShortcut(QKeySequence.Redo)

        self.addActions([undo, redo])
        self.undo_stack.indexChanged.connect(lambda _: self.invalidate())

        gui.separator(tBox)
        indBox = gui.indentedBox(tBox, sep=8)
        form = QFormLayout(
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )
        indBox.layout().addLayout(form)
        slider = gui.hSlider(
            indBox, self, "brushRadius", minValue=1, maxValue=100,
            createLabel=False
        )
        form.addRow("Radius:", slider)

        slider = gui.hSlider(
            indBox, self, "density", None, minValue=1, maxValue=100,
            createLabel=False
        )

        form.addRow("Intensity:", slider)
        self.btResetToInput = gui.button(
            tBox, self, "Reset to Input Data", self.reset_to_input)
        self.btResetToInput.setDisabled(True)

        gui.rubber(self.controlArea)
        gui.auto_commit(self.left_side, self, "autocommit",
                        "Send")

        # main area GUI
        viewbox = PaintViewBox(enableMouse=False)
        self.plotview = pg.PlotWidget(background="w", viewBox=viewbox)
        self.plotview.sizeHint = lambda: QSize(200, 100)  # Minimum size for 1-d painting
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.attr1)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.attr2)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)
        if not self.hasAttr2:
            self.plot.hideAxis('left')

        self.plot.hideButtons()
        self.plot.setXRange(0, 1, padding=0.01)

        self.mainArea.layout().addWidget(self.plotview)

        # enable brush tool
        self.toolActions.actions()[0].setChecked(True)
        self.set_current_tool(self.TOOLS[0][2])

        self.set_dimensions()
Пример #30
0
    def __new__(cls, *args, captionTitle=None, **kwargs):
        self = super().__new__(cls, None, cls.get_flags())
        QDialog.__init__(self, None, self.get_flags())
        OWComponent.__init__(self)
        WidgetMessagesMixin.__init__(self)
        WidgetSignalsMixin.__init__(self)

        stored_settings = kwargs.get('stored_settings', None)
        if self.settingsHandler:
            self.settingsHandler.initialize(self, stored_settings)

        self.signalManager = kwargs.get('signal_manager', None)
        self.__env = _asmappingproxy(kwargs.get("env", {}))

        self.graphButton = None
        self.report_button = None

        OWWidget.widget_id += 1
        self.widget_id = OWWidget.widget_id

        captionTitle = self.name if captionTitle is None else captionTitle

        # must be set without invoking setCaption
        self.captionTitle = captionTitle
        self.setWindowTitle(captionTitle)

        self.setFocusPolicy(Qt.StrongFocus)

        self.__blocking = False

        # flag indicating if the widget's position was already restored
        self.__was_restored = False

        self.__statusMessage = ""

        self.__msgwidget = None
        self.__msgchoice = 0

        self.__help_action = QAction(
            "Help", self, objectName="action-help", toolTip="Show help",
            enabled=False, visible=False, shortcut=QKeySequence(Qt.Key_F1)
        )
        self.addAction(self.__help_action)

        self.left_side = None
        self.controlArea = self.mainArea = self.buttonsArea = None
        self.__splitter = None
        if self.want_basic_layout:
            self.set_basic_layout()

        sc = QShortcut(QKeySequence(Qt.ShiftModifier | Qt.Key_F1), self)
        sc.activated.connect(self.__quicktip)

        sc = QShortcut(QKeySequence.Copy, self)
        sc.activated.connect(self.copy_to_clipboard)

        if self.controlArea is not None:
            # Otherwise, the first control has focus
            self.controlArea.setFocus(Qt.ActiveWindowFocusReason)

        if self.__splitter is not None:
            self.__splitter.handleClicked.connect(
                self.__toggleControlArea
            )
            sc = QShortcut(
                QKeySequence(Qt.ControlModifier | Qt.ShiftModifier | Qt.Key_D),
                self, autoRepeat=False)
            sc.activated.connect(self.__toggleControlArea)
        return self
Пример #31
0
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        form = self.layout().itemAt(0)
        assert isinstance(form, QFormLayout)
        self.ordered_cb = QCheckBox("Ordered",
                                    self,
                                    toolTip="Is this an ordered categorical.")
        self.ordered_cb.toggled.connect(self._set_ordered)
        #: A list model of discrete variable's values.
        self.values_model = itemmodels.PyListModel(
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        vlayout = QVBoxLayout(spacing=1, margin=0)
        self.values_edit = QListView(editTriggers=QListView.DoubleClicked
                                     | QListView.EditKeyPressed)
        self.values_edit.setItemDelegate(CategoriesEditDelegate(self))
        self.values_edit.setModel(self.values_model)
        self.values_model.dataChanged.connect(self.on_values_changed)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)
        self.values_model.layoutChanged.connect(
            self.on_value_selection_changed)
        self.values_model.rowsMoved.connect(self.on_value_selection_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout(spacing=1, margin=0)

        self.categories_action_group = group = QActionGroup(
            self, objectName="action-group-categories", enabled=False)
        self.move_value_up = QAction(
            "\N{UPWARDS ARROW}",
            group,
            toolTip="Move the selected item up.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier
                                  | Qt.Key_BracketLeft),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_up.triggered.connect(self.move_up)

        self.move_value_down = QAction(
            "\N{DOWNWARDS ARROW}",
            group,
            toolTip="Move the selected item down.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier
                                  | Qt.Key_BracketRight),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_down.triggered.connect(self.move_down)

        self.add_new_item = QAction(
            "+",
            group,
            objectName="action-add-item",
            toolTip="Append a new item.",
            shortcut=QKeySequence(QKeySequence.New),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.remove_item = QAction(
            "\N{MINUS SIGN}",
            group,
            objectName="action-remove-item",
            toolTip="Delete the selected item.",
            shortcut=QKeySequence(QKeySequence.Delete),
            shortcutContext=Qt.WidgetShortcut,
        )

        self.add_new_item.triggered.connect(self._add_category)
        self.remove_item.triggered.connect(self._remove_category)

        button1 = FixedSizeButton(self,
                                  defaultAction=self.move_value_up,
                                  accessibleName="Move up")
        button2 = FixedSizeButton(self,
                                  defaultAction=self.move_value_down,
                                  accessibleName="Move down")
        button3 = FixedSizeButton(self,
                                  defaultAction=self.add_new_item,
                                  accessibleName="Add")
        button4 = FixedSizeButton(self,
                                  defaultAction=self.remove_item,
                                  accessibleName="Remove")
        self.values_edit.addActions([
            self.move_value_up, self.move_value_down, self.add_new_item,
            self.remove_item
        ])
        hlayout.addWidget(button1)
        hlayout.addWidget(button2)
        hlayout.addSpacing(3)
        hlayout.addWidget(button3)
        hlayout.addWidget(button4)

        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        form.insertRow(1, "", self.ordered_cb)
        form.insertRow(2, "Values:", vlayout)

        QWidget.setTabOrder(self.name_edit, self.ordered_cb)
        QWidget.setTabOrder(self.ordered_cb, self.values_edit)

        QWidget.setTabOrder(self.values_edit, button1)
        QWidget.setTabOrder(button1, button2)
        QWidget.setTabOrder(button2, button3)
        QWidget.setTabOrder(button3, button4)

    def set_data(self, var, transform=()):
        # type: (Optional[Categorical], Sequence[Transform]) -> None
        """
        Set the variable to edit.
        """
        # pylint: disable=too-many-branches
        super().set_data(var, transform)
        tr = None  # type: Optional[CategoriesMapping]
        ordered = None  # type: Optional[ChangeOrdered]
        for tr_ in transform:
            if isinstance(tr_, CategoriesMapping):
                tr = tr_
            if isinstance(tr_, ChangeOrdered):
                ordered = tr_

        items = []
        if tr is not None:
            ci_index = {c: i for i, c in enumerate(var.categories)}
            for ci, cj in tr.mapping:
                if ci is None and cj is not None:
                    # level added
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.Added,
                        SourcePosRole: None
                    }
                elif ci is not None and cj is None:
                    # ci level dropped
                    item = {
                        Qt.EditRole: ci,
                        EditStateRole: ItemEditState.Dropped,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                elif ci is not None and cj is not None:
                    # rename or reorder
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.NoState,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                else:
                    assert False, "invalid mapping: {!r}".format(tr.mapping)
                items.append(item)
        elif var is not None:
            items = [{
                Qt.EditRole: c,
                EditStateRole: ItemEditState.NoState,
                SourcePosRole: i,
                SourceNameRole: c
            } for i, c in enumerate(var.categories)]
        else:
            items = []

        with disconnected(self.values_model.dataChanged,
                          self.on_values_changed):
            self.values_model.clear()
            self.values_model.insertRows(0, len(items))
            for i, item in enumerate(items):
                self.values_model.setItemData(self.values_model.index(i, 0),
                                              item)
        if ordered is not None:
            self.ordered_cb.setChecked(ordered.ordered)
        elif var is not None:
            self.ordered_cb.setChecked(isinstance(var, Ordered))
        self.add_new_item.actionGroup().setEnabled(var is not None)

    def __categories_mapping(self):
        # type: () -> CategoriesMappingType
        model = self.values_model
        source = self.var.categories

        res = []
        for i in range(model.rowCount()):
            midx = model.index(i, 0)
            category = midx.data(Qt.EditRole)
            source_pos = midx.data(SourcePosRole)  # type: Optional[int]
            if source_pos is not None:
                source_name = source[source_pos]
            else:
                source_name = None
            state = midx.data(EditStateRole)
            if state == ItemEditState.Dropped:
                res.append((source_name, None))
            elif state == ItemEditState.Added:
                res.append((None, category))
            else:
                res.append((source_name, category))
        return res

    def get_data(self):
        """Retrieve the modified variable
        """
        var, tr = super().get_data()
        if var is None:
            return var, tr
        mapping = self.__categories_mapping()
        if any(_1 != _2 or _2 != _3
               for (_1, _2), _3 in zip_longest(mapping, var.categories)):
            tr.append(CategoriesMapping(mapping))
        ordered = self.ordered_cb.isChecked()
        if ordered != isinstance(var, Ordered):
            tr.append(ChangeOrdered(ordered))
        return var, tr

    def clear(self):
        """Clear the model state.
        """
        super().clear()
        self.values_model.clear()

    def move_rows(self, rows, offset):
        if not rows:
            return
        assert len(rows) == 1
        i = rows[0].row()
        if offset > 0:
            offset += 1
        self.values_model.moveRows(QModelIndex(), i, 1, QModelIndex(),
                                   i + offset)
        self.variable_changed.emit()

    def move_up(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, -1)

    def move_down(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, 1)

    @Slot()
    def on_values_changed(self):
        self.variable_changed.emit()

    @Slot()
    def on_value_selection_changed(self):
        rows = self.values_edit.selectionModel().selectedRows()
        if rows:
            i = rows[0].row()
            self.move_value_up.setEnabled(i)
            self.move_value_down.setEnabled(
                i != self.values_model.rowCount() - 1)
        else:
            self.move_value_up.setEnabled(False)
            self.move_value_down.setEnabled(False)

    def _remove_category(self):
        """
        Remove the current selected category.

        If the item is an existing category present in the source variable it
        is marked as removed in the view. But if it was added in the set
        transformation it is removed entirely from the model and view.
        """
        view = self.values_edit
        rows = view.selectionModel().selectedRows(0)
        if not rows:
            return
        assert len(rows) == 1
        index = rows[0]  # type: QModelIndex
        model = index.model()
        state = index.data(EditStateRole)
        pos = index.data(Qt.UserRole)
        if pos is not None and pos >= 0:
            # existing level -> only mark/toggle its dropped state
            model.setData(
                index, ItemEditState.Dropped if state != ItemEditState.Dropped
                else ItemEditState.NoState, EditStateRole)
        elif state == ItemEditState.Added:
            # new level -> remove it
            model.removeRow(index.row())
        else:
            assert False, "invalid state '{}' for {}" \
                .format(state, index.row())

    def _add_category(self):
        """
        Add a new category
        """
        view = self.values_edit
        model = view.model()

        with disconnected(model.dataChanged, self.on_values_changed,
                          Qt.UniqueConnection):
            row = model.rowCount()
            if not model.insertRow(model.rowCount()):
                return
            index = model.index(row, 0)
            model.setItemData(
                index, {
                    Qt.EditRole: "",
                    SourcePosRole: None,
                    EditStateRole: ItemEditState.Added
                })
            view.setCurrentIndex(index)
            view.edit(index)
        self.on_values_changed()

    def _set_ordered(self, ordered):
        self.ordered_cb.setChecked(ordered)
        self.variable_changed.emit()
Пример #32
0
    def test_tool_grid(self):
        w = ToolGrid()

        w.show()
        self.app.processEvents()

        def buttonsOrderedVisual():
            # Process layout events so the buttons have right positions
            self.app.processEvents()
            buttons = w.findChildren(QToolButton)
            return list(sorted(buttons, key=lambda b: (b.y(), b.x())))

        def buttonsOrderedLogical():
            return list(map(w.buttonForAction, w.actions()))

        def assertOrdered():
            self.assertSequenceEqual(buttonsOrderedLogical(),
                                     buttonsOrderedVisual())

        action_a = QAction("A", w)
        action_b = QAction("B", w)
        action_c = QAction("C", w)
        action_d = QAction("D", w)

        w.addAction(action_b)
        w.insertAction(0, action_a)
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b])
        assertOrdered()

        w.addAction(action_d)
        w.insertAction(action_d, action_c)

        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_c, action_d])
        assertOrdered()

        w.removeAction(action_c)
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_d])

        assertOrdered()

        w.removeAction(action_a)
        self.assertSequenceEqual(w.actions(),
                                 [action_b, action_d])

        assertOrdered()

        w.insertAction(0, action_a)
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_d])

        assertOrdered()

        w.setColumnCount(2)
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_d])

        assertOrdered()

        w.insertAction(2, action_c)
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_c, action_d])
        assertOrdered()

        w.clear()
        # test no 'before' action edge case
        w.insertAction(0, action_a)
        self.assertIs(action_a, w.actions()[0])
        w.insertAction(1, action_b)
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b])

        w.clear()
        w.setActions([action_a, action_b, action_c, action_d])
        self.assertSequenceEqual(w.actions(),
                                 [action_a, action_b, action_c, action_d])
        assertOrdered()

        triggered_actions = []

        def p(action):
            print(action.text())

        w.actionTriggered.connect(p)
        w.actionTriggered.connect(triggered_actions.append)
        action_a.trigger()

        w.show()
        self.app.exec_()
Пример #33
0
class VariableEditor(QWidget):
    """An editor widget for a variable.

    Can edit the variable name, and its attributes dictionary.

    """
    variable_changed = Signal()

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.var = None
        self.setup_gui()

    def setup_gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.main_form = QFormLayout()
        self.main_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        layout.addLayout(self.main_form)

        self._setup_gui_name()
        self._setup_gui_labels()

    def _setup_gui_name(self):
        class OrangeLineEdit(QLineEdit):
            def keyPressEvent(self, event):
                if event.key() in [Qt.Key_Return, Qt.Key_Enter]:
                    self.parent().on_name_changed()
                else:
                    super().keyPressEvent(event)

        self.name_edit = OrangeLineEdit()
        self.main_form.addRow("Name:", self.name_edit)
        self.name_edit.editingFinished.connect(self.on_name_changed)

    def _setup_gui_labels(self):
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.setSpacing(1)

        self.labels_edit = QTreeView()
        self.labels_edit.setEditTriggers(QTreeView.CurrentChanged)
        self.labels_edit.setRootIsDecorated(False)

        self.labels_model = DictItemsModel()
        self.labels_edit.setModel(self.labels_model)

        self.labels_edit.selectionModel().selectionChanged.connect(
            self.on_label_selection_changed)

        # Necessary signals to know when the labels change
        self.labels_model.dataChanged.connect(self.on_labels_changed)
        self.labels_model.rowsInserted.connect(self.on_labels_changed)
        self.labels_model.rowsRemoved.connect(self.on_labels_changed)

        vlayout.addWidget(self.labels_edit)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        hlayout.setSpacing(1)
        self.add_label_action = QAction("+",
                                        self,
                                        toolTip="Add a new label.",
                                        triggered=self.on_add_label,
                                        enabled=False,
                                        shortcut=QKeySequence(
                                            QKeySequence.New))

        self.remove_label_action = QAction(unicodedata.lookup("MINUS SIGN"),
                                           self,
                                           toolTip="Remove selected label.",
                                           triggered=self.on_remove_label,
                                           enabled=False,
                                           shortcut=QKeySequence(
                                               QKeySequence.Delete))

        button_size = gui.toolButtonSizeHint()
        button_size = QSize(button_size, button_size)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.add_label_action)
        hlayout.addWidget(button)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.remove_label_action)
        hlayout.addWidget(button)
        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        self.main_form.addRow("Labels:", vlayout)

    def set_data(self, var):
        """Set the variable to edit.
        """
        self.clear()
        self.var = var

        if var is not None:
            self.name_edit.setText(var.name)
            self.labels_model.set_dict(dict(var.attributes))
            self.add_label_action.setEnabled(True)
        else:
            self.add_label_action.setEnabled(False)
            self.remove_label_action.setEnabled(False)

    def get_data(self):
        """Retrieve the modified variable.
        """
        name = str(self.name_edit.text()).strip()
        labels = self.labels_model.get_dict()

        # Is the variable actually changed.
        if self.var is not None and not self.is_same():
            var = type(self.var)(name)
            var.attributes.update(labels)
            self.var = var
        else:
            var = self.var

        return var

    def is_legal(self):
        name = str(self.name_edit.text()).strip()
        return not len(name) == 0

    def is_same(self):
        """Is the current model state the same as the input.
        """
        name = str(self.name_edit.text()).strip()
        labels = self.labels_model.get_dict()
        return (self.var is not None and name == self.var.name
                and labels == self.var.attributes)

    def clear(self):
        """Clear the editor state.
        """
        self.var = None
        self.name_edit.setText("")
        self.labels_model.set_dict({})

    def maybe_commit(self):
        if not self.is_same() and self.is_legal():
            self.commit()

    def commit(self):
        """Emit a ``variable_changed()`` signal.
        """
        self.variable_changed.emit()

    @Slot()
    def on_name_changed(self):
        self.maybe_commit()

    @Slot()
    def on_labels_changed(self, *args):
        self.maybe_commit()

    @Slot()
    def on_add_label(self):
        self.labels_model.appendRow([QStandardItem(""), QStandardItem("")])
        row = self.labels_model.rowCount() - 1
        index = self.labels_model.index(row, 0)
        self.labels_edit.edit(index)

    @Slot()
    def on_remove_label(self):
        rows = self.labels_edit.selectionModel().selectedRows()
        if rows:
            row = rows[0]
            self.labels_model.removeRow(row.row())

    @Slot()
    def on_label_selection_changed(self):
        selected = self.labels_edit.selectionModel().selectedRows()
        self.remove_label_action.setEnabled(bool(len(selected)))
Пример #34
0
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        form = self.layout().itemAt(0)
        assert isinstance(form, QFormLayout)

        #: A list model of discrete variable's values.
        self.values_model = itemmodels.PyListModel(
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
        )

        vlayout = QVBoxLayout(spacing=1, margin=0)
        self.values_edit = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed
        )
        self.values_edit.setItemDelegate(CategoriesEditDelegate(self))
        self.values_edit.setModel(self.values_model)
        self.values_model.dataChanged.connect(self.on_values_changed)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)
        self.values_model.layoutChanged.connect(self.on_value_selection_changed)
        self.values_model.rowsMoved.connect(self.on_value_selection_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout(spacing=1, margin=0)

        self.categories_action_group = group = QActionGroup(
            self, objectName="action-group-categories", enabled=False
        )
        self.move_value_up = QAction(
            "\N{UPWARDS ARROW}", group,
            toolTip="Move the selected item up.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier |
                                  Qt.Key_BracketLeft),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_up.triggered.connect(self.move_up)

        self.move_value_down = QAction(
            "\N{DOWNWARDS ARROW}", group,
            toolTip="Move the selected item down.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier |
                                  Qt.Key_BracketRight),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_down.triggered.connect(self.move_down)

        self.add_new_item = QAction(
            "+", group,
            objectName="action-add-item",
            toolTip="Append a new item.",
            shortcut=QKeySequence(QKeySequence.New),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.remove_item = QAction(
            "\N{MINUS SIGN}", group,
            objectName="action-remove-item",
            toolTip="Delete the selected item.",
            shortcut=QKeySequence(QKeySequence.Delete),
            shortcutContext=Qt.WidgetShortcut,
        )

        self.add_new_item.triggered.connect(self._add_category)
        self.remove_item.triggered.connect(self._remove_category)

        button1 = FixedSizeButton(
            self, defaultAction=self.move_value_up,
            accessibleName="Move up"
        )
        button2 = FixedSizeButton(
            self, defaultAction=self.move_value_down,
            accessibleName="Move down"
        )
        button3 = FixedSizeButton(
            self, defaultAction=self.add_new_item,
            accessibleName="Add"
        )
        button4 = FixedSizeButton(
            self, defaultAction=self.remove_item,
            accessibleName="Remove"
        )
        self.values_edit.addActions([self.move_value_up, self.move_value_down,
                                     self.add_new_item, self.remove_item])
        hlayout.addWidget(button1)
        hlayout.addWidget(button2)
        hlayout.addSpacing(3)
        hlayout.addWidget(button3)
        hlayout.addWidget(button4)

        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        form.insertRow(1, "Values:", vlayout)

        QWidget.setTabOrder(self.name_edit, self.values_edit)
        QWidget.setTabOrder(self.values_edit, button1)
        QWidget.setTabOrder(button1, button2)
        QWidget.setTabOrder(button2, button3)
        QWidget.setTabOrder(button3, button4)

    def set_data(self, var, transform=()):
        # type: (Optional[Categorical], Sequence[Transform]) -> None
        """
        Set the variable to edit.
        """
        super().set_data(var, transform)
        tr = None  # type: Optional[CategoriesMapping]
        for tr_ in transform:
            if isinstance(tr_, CategoriesMapping):
                tr = tr_

        items = []
        if tr is not None:
            ci_index = {c: i for i, c in enumerate(var.categories)}
            for ci, cj in tr.mapping:
                if ci is None and cj is not None:
                    # level added
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.Added,
                        SourcePosRole: None
                    }
                elif ci is not None and cj is None:
                    # ci level dropped
                    item = {
                        Qt.EditRole: ci,
                        EditStateRole: ItemEditState.Dropped,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                elif ci is not None and cj is not None:
                    # rename or reorder
                    item = {
                        Qt.EditRole: cj,
                        EditStateRole: ItemEditState.NoState,
                        SourcePosRole: ci_index[ci],
                        SourceNameRole: ci
                    }
                else:
                    assert False, "invalid mapping: {!r}".format(tr.mapping)
                items.append(item)
        elif var is not None:
            items = [
                {Qt.EditRole: c,
                 EditStateRole: ItemEditState.NoState,
                 SourcePosRole: i,
                 SourceNameRole: c}
                for i, c in enumerate(var.categories)
            ]
        else:
            items = []

        with disconnected(self.values_model.dataChanged,
                          self.on_values_changed):
            self.values_model.clear()
            self.values_model.insertRows(0, len(items))
            for i, item in enumerate(items):
                self.values_model.setItemData(
                    self.values_model.index(i, 0),
                    item
                )
        self.add_new_item.actionGroup().setEnabled(var is not None)

    def __categories_mapping(self):
        # type: () -> CategoriesMappingType
        model = self.values_model
        source = self.var.categories

        res = []
        for i in range(model.rowCount()):
            midx = model.index(i, 0)
            category = midx.data(Qt.EditRole)
            source_pos = midx.data(SourcePosRole)  # type: Optional[int]
            if source_pos is not None:
                source_name = source[source_pos]
            else:
                source_name = None
            state = midx.data(EditStateRole)
            if state == ItemEditState.Dropped:
                res.append((source_name, None))
            elif state == ItemEditState.Added:
                res.append((None, category))
            else:
                res.append((source_name, category))
        return res

    def get_data(self):
        """Retrieve the modified variable
        """
        var, tr = super().get_data()
        if var is None:
            return var, tr
        mapping = self.__categories_mapping()
        if any(_1 != _2 or _2 != _3
               for (_1, _2), _3 in zip_longest(mapping, var.categories)):
            tr.append(CategoriesMapping(mapping))
        return var, tr

    def clear(self):
        """Clear the model state.
        """
        super().clear()
        self.values_model.clear()

    def move_rows(self, rows, offset):
        if not rows:
            return
        assert len(rows) == 1
        i = rows[0].row()
        if offset > 0:
            offset += 1
        self.values_model.moveRows(QModelIndex(), i, 1, QModelIndex(), i + offset)
        self.variable_changed.emit()

    def move_up(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, -1)

    def move_down(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, 1)

    @Slot()
    def on_values_changed(self):
        self.variable_changed.emit()

    @Slot()
    def on_value_selection_changed(self):
        rows = self.values_edit.selectionModel().selectedRows()
        if rows:
            i = rows[0].row()
            self.move_value_up.setEnabled(i)
            self.move_value_down.setEnabled(i != self.values_model.rowCount() - 1)
        else:
            self.move_value_up.setEnabled(False)
            self.move_value_down.setEnabled(False)

    def _remove_category(self):
        """
        Remove the current selected category.

        If the item is an existing category present in the source variable it
        is marked as removed in the view. But if it was added in the set
        transformation it is removed entirely from the model and view.
        """
        view = self.values_edit
        rows = view.selectionModel().selectedRows(0)
        if not rows:
            return
        assert len(rows) == 1
        index = rows[0]  # type: QModelIndex
        model = index.model()
        state = index.data(EditStateRole)
        pos = index.data(Qt.UserRole)
        if pos is not None and pos >= 0:
            # existing level -> only mark/toggle its dropped state
            model.setData(
                index,
                ItemEditState.Dropped if state != ItemEditState.Dropped
                else ItemEditState.NoState,
                EditStateRole)
        elif state == ItemEditState.Added:
            # new level -> remove it
            model.removeRow(index.row())
        else:
            assert False, "invalid state '{}' for {}" \
                .format(state, index.row())

    def _add_category(self):
        """
        Add a new category
        """
        view = self.values_edit
        model = view.model()

        with disconnected(model.dataChanged, self.on_values_changed,
                          Qt.UniqueConnection):
            row = model.rowCount()
            if not model.insertRow(model.rowCount()):
                return
            index = model.index(row, 0)
            model.setItemData(
                index, {
                    Qt.EditRole: "",
                    SourcePosRole: None,
                    EditStateRole: ItemEditState.Added
                }
            )
            view.setCurrentIndex(index)
            view.edit(index)
        self.on_values_changed()
Пример #35
0
    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)
        self.var = None  # type: Optional[Variable]

        layout = QVBoxLayout()
        self.setLayout(layout)

        self.form = form = QFormLayout(
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow,
            objectName="editor-form-layout")
        layout.addLayout(self.form)

        self.name_edit = QLineEdit(objectName="name-editor")
        self.name_edit.editingFinished.connect(
            lambda: self.name_edit.isModified() and self.on_name_changed())
        form.addRow("Name:", self.name_edit)

        vlayout = QVBoxLayout(margin=0, spacing=1)
        self.labels_edit = view = QTreeView(
            objectName="annotation-pairs-edit",
            rootIsDecorated=False,
            editTriggers=QTreeView.DoubleClicked | QTreeView.EditKeyPressed,
        )
        self.labels_model = model = DictItemsModel()
        view.setModel(model)

        view.selectionModel().selectionChanged.connect(
            self.on_label_selection_changed)

        agrp = QActionGroup(view, objectName="annotate-action-group")
        action_add = QAction("+",
                             self,
                             objectName="action-add-label",
                             toolTip="Add a new label.",
                             shortcut=QKeySequence(QKeySequence.New),
                             shortcutContext=Qt.WidgetShortcut)
        action_delete = QAction("\N{MINUS SIGN}",
                                self,
                                objectName="action-delete-label",
                                toolTip="Remove selected label.",
                                shortcut=QKeySequence(QKeySequence.Delete),
                                shortcutContext=Qt.WidgetShortcut)
        agrp.addAction(action_add)
        agrp.addAction(action_delete)
        view.addActions([action_add, action_delete])

        def add_label():
            row = [QStandardItem(), QStandardItem()]
            model.appendRow(row)
            idx = model.index(model.rowCount() - 1, 0)
            view.setCurrentIndex(idx)
            view.edit(idx)

        def remove_label():
            rows = view.selectionModel().selectedRows(0)
            if rows:
                assert len(rows) == 1
                idx = rows[0].row()
                model.removeRow(idx)

        action_add.triggered.connect(add_label)
        action_delete.triggered.connect(remove_label)
        agrp.setEnabled(False)

        self.add_label_action = action_add
        self.remove_label_action = action_delete

        # Necessary signals to know when the labels change
        model.dataChanged.connect(self.on_labels_changed)
        model.rowsInserted.connect(self.on_labels_changed)
        model.rowsRemoved.connect(self.on_labels_changed)

        vlayout.addWidget(self.labels_edit)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        button = FixedSizeButton(
            self,
            defaultAction=self.add_label_action,
            accessibleName="Add",
        )
        hlayout.addWidget(button)

        button = FixedSizeButton(
            self,
            defaultAction=self.remove_label_action,
            accessibleName="Remove",
        )

        hlayout.addWidget(button)
        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)
        form.addRow("Labels:", vlayout)
Пример #36
0
class SplitterResizer(QObject):
    """
    An object able to control the size of a widget in a QSplitter instance.
    """
    def __init__(self, parent=None):
        super().__init__(parent)
        self.__splitter = None
        self.__widget = None
        self.__updateOnShow = True  # Need __update on next show event
        self.__animationEnabled = True
        self.__size = -1
        self.__expanded = False
        self.__animation = QPropertyAnimation(
            self, b"size_", self, duration=200
        )
        self.__action = QAction("toogle-expanded", self, checkable=True)
        self.__action.triggered[bool].connect(self.setExpanded)

    def setSize(self, size):
        """Set the size of the controlled widget (either width or height
        depending on the orientation).

        .. note::
            The controlled widget's size is only updated when it it is shown.
        """
        if self.__size != size:
            self.__size = size
            self.__update()

    def size(self):
        """Return the size of the widget in the splitter (either height of
        width) depending on the splitter orientation.

        """
        if self.__splitter and self.__widget:
            index = self.__splitter.indexOf(self.__widget)
            sizes = self.__splitter.sizes()
            return sizes[index]
        else:
            return -1

    size_ = Property(int, fget=size, fset=setSize)

    def setAnimationEnabled(self, enable):
        """Enable/disable animation.
        """
        self.__animation.setDuration(0 if enable else 200)

    def animationEnabled(self):
        return self.__animation.duration() == 0

    def setSplitterAndWidget(self, splitter, widget):
        """Set the QSplitter and QWidget instance the resizer should control.

        .. note:: the widget must be in the splitter.

        """
        if splitter and widget and not splitter.indexOf(widget) > 0:
            raise ValueError("Widget must be in a spliter.")

        if self.__widget is not None:
            self.__widget.removeEventFilter(self)
        if self.__splitter is not None:
            self.__splitter.removeEventFilter(self)

        self.__splitter = splitter
        self.__widget = widget

        if widget is not None:
            widget.installEventFilter(self)
        if splitter is not None:
            splitter.installEventFilter(self)

        self.__update()

        size = self.size()
        if self.__expanded and size == 0:
            self.open()
        elif not self.__expanded and size > 0:
            self.close()

    def toogleExpandedAction(self):
        """Return a QAction that can be used to toggle expanded state.
        """
        return self.__action

    def open(self):
        """Open the controlled widget (expand it to sizeHint).
        """
        self.__expanded = True
        self.__action.setChecked(True)

        if self.__splitter is None or self.__widget is None:
            return

        hint = self.__widget.sizeHint()

        if self.__splitter.orientation() == Qt.Vertical:
            end = hint.height()
        else:
            end = hint.width()

        self.__animation.setStartValue(0)
        self.__animation.setEndValue(end)
        self.__animation.start()

    def close(self):
        """Close the controlled widget (shrink to size 0).
        """
        self.__expanded = False
        self.__action.setChecked(False)

        if self.__splitter is None or self.__widget is None:
            return

        self.__animation.setStartValue(self.size())
        self.__animation.setEndValue(0)
        self.__animation.start()

    def setExpanded(self, expanded):
        """Set the expanded state.

        """
        if self.__expanded != expanded:
            if expanded:
                self.open()
            else:
                self.close()

    def expanded(self):
        """Return the expanded state.
        """
        return self.__expanded

    def __update(self):
        """Update the splitter sizes.
        """
        if self.__splitter and self.__widget:
            if sum(self.__splitter.sizes()) == 0:
                # schedule update on next show event
                self.__updateOnShow = True
                return

            splitter = self.__splitter
            index = splitter.indexOf(self.__widget)
            sizes = splitter.sizes()
            current = sizes[index]
            diff = current - self.__size
            sizes[index] = self.__size
            sizes[index - 1] = sizes[index - 1] + diff
            self.__splitter.setSizes(sizes)

    def eventFilter(self, obj, event):
        if event.type() == QEvent.Resize and obj is self.__widget and \
                self.__animation.state() == QPropertyAnimation.Stopped:
            # Update the expanded state when the user opens/closes the widget
            # by dragging the splitter handle.
            if self.__splitter.orientation() == Qt.Vertical:
                size = event.size().height()
            else:
                size = event.size().width()

            if self.__expanded and size == 0:
                self.__action.setChecked(False)
                self.__expanded = False
            elif not self.__expanded and size > 0:
                self.__action.setChecked(True)
                self.__expanded = True

        if event.type() == QEvent.Show and obj is self.__splitter and \
                self.__updateOnShow:
            # Update the splitter state after receiving valid geometry
            self.__updateOnShow = False
            self.__update()
        return super().eventFilter(obj, event)
Пример #37
0
class DiscreteVariableEditor(VariableEditor):
    """An editor widget for editing a discrete variable.

    Extends the :class:`VariableEditor` to enable editing of
    variables values.

    """
    def setup_gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.main_form = QFormLayout()
        self.main_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        layout.addLayout(self.main_form)

        self._setup_gui_name()
        self._setup_gui_values()
        self._setup_gui_labels()

    def _setup_gui_values(self):
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.setSpacing(1)

        self.values_edit = QListView()
        self.values_edit.setEditTriggers(QTreeView.CurrentChanged)
        self.values_model = itemmodels.PyListModel(flags=Qt.ItemIsSelectable | \
                                        Qt.ItemIsEnabled | Qt.ItemIsEditable)
        self.values_edit.setModel(self.values_model)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)

        self.values_model.dataChanged.connect(self.on_values_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        hlayout.setSpacing(1)
        self.move_value_up = QAction(unicodedata.lookup("UPWARDS ARROW"),
                                     self,
                                     toolTip="Move up.",
                                     triggered=self.move_up,
                                     enabled=False,
                                     shortcut=QKeySequence(QKeySequence.New))

        self.move_value_down = QAction(unicodedata.lookup("DOWNWARDS ARROW"),
                                       self,
                                       toolTip="Move down.",
                                       triggered=self.move_down,
                                       enabled=False,
                                       shortcut=QKeySequence(
                                           QKeySequence.Delete))

        button_size = gui.toolButtonSizeHint()
        button_size = QSize(button_size, button_size)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.move_value_up)
        hlayout.addWidget(button)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.move_value_down)
        hlayout.addWidget(button)
        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        self.main_form.addRow("Values:", vlayout)

    def set_data(self, var):
        """Set the variable to edit
        """
        VariableEditor.set_data(self, var)
        self.values_model[:] = list(var.values) if var is not None else []

    def get_data(self):
        """Retrieve the modified variable
        """
        name = str(self.name_edit.text()).strip()
        labels = self.labels_model.get_dict()
        values = map(str, self.values_model)

        if self.var is not None and not self.is_same():
            var = type(self.var)(name, values=values)
            var.attributes.update(labels)
            self.var = var
        else:
            var = self.var

        return var

    def is_same(self):
        """Is the current model state the same as the input.
        """
        values = list(map(str, self.values_model))
        return (VariableEditor.is_same(self) and self.var is not None
                and self.var.values == values)

    def clear(self):
        """Clear the model state.
        """
        VariableEditor.clear(self)
        self.values_model.clear()

    def move_rows(self, rows, offset):
        i = rows[0].row()
        self.values_model[i], self.values_model[i+offset] = \
            self.values_model[i+offset], self.values_model[i]
        self.maybe_commit()

    def move_up(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, -1)

    def move_down(self):
        rows = self.values_edit.selectionModel().selectedRows()
        self.move_rows(rows, 1)

    @Slot()
    def on_values_changed(self):
        self.maybe_commit()

    @Slot()
    def on_value_selection_changed(self):
        i = self.values_edit.selectionModel().selectedRows()[0].row()
        self.move_value_up.setEnabled(i)
        self.move_value_down.setEnabled(i != len(self.var.values) - 1)
Пример #38
0
class CanvasView(QGraphicsView):
    """Canvas View handles the zooming.
    """

    def __init__(self, *args):
        super().__init__(*args)
        self.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        self.__backgroundIcon = QIcon()

        self.__autoScroll = False
        self.__autoScrollMargin = 16
        self.__autoScrollTimer = QTimer(self)
        self.__autoScrollTimer.timeout.connect(self.__autoScrollAdvance)

        # scale factor accumulating partial increments from wheel events
        self.__zoomLevel = 100
        # effective scale level(rounded to whole integers)
        self.__effectiveZoomLevel = 100

        self.__zoomInAction = QAction(
            self.tr("Zoom in"), self, objectName="action-zoom-in",
            shortcut=QKeySequence.ZoomIn,
            triggered=self.zoomIn,
        )

        self.__zoomOutAction = QAction(
            self.tr("Zoom out"), self, objectName="action-zoom-out",
            shortcut=QKeySequence.ZoomOut,
            triggered=self.zoomOut
        )
        self.__zoomResetAction = QAction(
            self.tr("Reset Zoom"), self, objectName="action-zoom-reset",
            triggered=self.zoomReset,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0)
        )

    def setScene(self, scene):
        super().setScene(scene)
        self._ensureSceneRect(scene)

    def _ensureSceneRect(self, scene):
        r = scene.addRect(QRectF(0, 0, 400, 400))
        scene.sceneRect()
        scene.removeItem(r)

    def setAutoScrollMargin(self, margin):
        self.__autoScrollMargin = margin

    def autoScrollMargin(self):
        return self.__autoScrollMargin

    def setAutoScroll(self, enable):
        self.__autoScroll = enable

    def autoScroll(self):
        return self.__autoScroll

    def mousePressEvent(self, event):
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            if not self.__autoScrollTimer.isActive() and \
                    self.__shouldAutoScroll(event.pos()):
                self.__startAutoScroll()
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() & Qt.LeftButton:
            self.__stopAutoScroll()
        return super().mouseReleaseEvent(event)

    def wheelEvent(self, event: QWheelEvent):
        if event.modifiers() & Qt.ControlModifier \
                and event.buttons() == Qt.NoButton:
            delta = event.angleDelta().y()
            # use mouse position as anchor while zooming
            anchor = self.transformationAnchor()
            self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
            self.__setZoomLevel(self.__zoomLevel + 10 * delta / 120)
            self.setTransformationAnchor(anchor)
            event.accept()
        else:
            super().wheelEvent(event)

    def zoomIn(self):
        self.__setZoomLevel(self.__zoomLevel + 10)

    def zoomOut(self):
        self.__setZoomLevel(self.__zoomLevel - 10)

    def zoomReset(self):
        """
        Reset the zoom level.
        """
        self.__setZoomLevel(100)

    def zoomLevel(self):
        # type: () -> float
        """
        Return the current zoom level.

        Level is expressed in percentages; 100 is unscaled, 50 is half size, ...
        """
        return self.__effectiveZoomLevel

    def setZoomLevel(self, level):
        self.__setZoomLevel(level)

    def __setZoomLevel(self, scale):
        self.__zoomLevel = max(30, min(scale, 300))
        scale = round(self.__zoomLevel)
        self.__zoomOutAction.setEnabled(scale != 30)
        self.__zoomInAction.setEnabled(scale != 300)
        if self.__effectiveZoomLevel != scale:
            self.__effectiveZoomLevel = scale
            transform = QTransform()
            transform.scale(scale / 100, scale / 100)
            self.setTransform(transform)
            self.zoomLevelChanged.emit(scale)

    zoomLevelChanged = Signal(float)
    zoomLevel_ = Property(
        float, zoomLevel, setZoomLevel, notify=zoomLevelChanged
    )

    def __shouldAutoScroll(self, pos):
        if self.__autoScroll:
            margin = self.__autoScrollMargin
            viewrect = self.contentsRect()
            rect = viewrect.adjusted(margin, margin, -margin, -margin)
            # only do auto scroll when on the viewport's margins
            return not rect.contains(pos) and viewrect.contains(pos)
        else:
            return False

    def __startAutoScroll(self):
        self.__autoScrollTimer.start(10)
        log.debug("Auto scroll timer started")

    def __stopAutoScroll(self):
        if self.__autoScrollTimer.isActive():
            self.__autoScrollTimer.stop()
            log.debug("Auto scroll timer stopped")

    def __autoScrollAdvance(self):
        """Advance the auto scroll
        """
        pos = QCursor.pos()
        pos = self.mapFromGlobal(pos)
        margin = self.__autoScrollMargin

        vvalue = self.verticalScrollBar().value()
        hvalue = self.horizontalScrollBar().value()

        vrect = QRect(0, 0, self.width(), self.height())

        # What should be the speed
        advance = 10

        # We only do auto scroll if the mouse is inside the view.
        if vrect.contains(pos):
            if pos.x() < vrect.left() + margin:
                self.horizontalScrollBar().setValue(hvalue - advance)
            if pos.y() < vrect.top() + margin:
                self.verticalScrollBar().setValue(vvalue - advance)
            if pos.x() > vrect.right() - margin:
                self.horizontalScrollBar().setValue(hvalue + advance)
            if pos.y() > vrect.bottom() - margin:
                self.verticalScrollBar().setValue(vvalue + advance)

            if self.verticalScrollBar().value() == vvalue and \
                    self.horizontalScrollBar().value() == hvalue:
                self.__stopAutoScroll()
        else:
            self.__stopAutoScroll()

        log.debug("Auto scroll advance")

    def setBackgroundIcon(self, icon):
        if not isinstance(icon, QIcon):
            raise TypeError("A QIcon expected.")

        if self.__backgroundIcon != icon:
            self.__backgroundIcon = icon
            self.viewport().update()

    def backgroundIcon(self):
        return QIcon(self.__backgroundIcon)

    def drawBackground(self, painter, rect):
        super().drawBackground(painter, rect)

        if not self.__backgroundIcon.isNull():
            painter.setClipRect(rect)
            vrect = QRect(QPoint(0, 0), self.viewport().size())
            vrect = self.mapToScene(vrect).boundingRect()

            pm = self.__backgroundIcon.pixmap(
                vrect.size().toSize().boundedTo(QSize(200, 200))
            )
            pmrect = QRect(QPoint(0, 0), pm.size())
            pmrect.moveCenter(vrect.center().toPoint())
            if rect.toRect().intersects(pmrect):
                painter.drawPixmap(pmrect, pm)
Пример #39
0
    def __init__(self):
        super().__init__()

        self.results = None
        self.classifier_names = []
        self.perf_line = None
        self.colors = []
        self._curve_data = {}
        self._plot_curves = {}
        self._rocch = None
        self._perf_line = None
        self._tooltip_cache = None

        box = gui.vBox(self.controlArea, "Plot")
        self.target_cb = gui.comboBox(
            box, self, "target_index",
            label="Target", orientation=Qt.Horizontal,
            callback=self._on_target_changed,
            contentsLength=8, searchable=True)

        gui.widgetLabel(box, "Classifiers")
        line_height = 4 * QFontMetrics(self.font()).lineSpacing()
        self.classifiers_list_box = gui.listBox(
            box, self, "selected_classifiers", "classifier_names",
            selectionMode=QListView.MultiSelection,
            callback=self._on_classifiers_changed,
            sizeHint=QSize(0, line_height))

        abox = gui.vBox(self.controlArea, "Curves")
        gui.comboBox(abox, self, "roc_averaging",
                     items=["Merge Predictions from Folds", "Mean TP Rate",
                            "Mean TP and FP at Threshold", "Show Individual Curves"],
                     callback=self._replot)

        gui.checkBox(abox, self, "display_convex_curve",
                     "Show convex ROC curves", callback=self._replot)
        gui.checkBox(abox, self, "display_convex_hull",
                     "Show ROC convex hull", callback=self._replot)

        box = gui.vBox(self.controlArea, "Analysis")

        gui.checkBox(box, self, "display_def_threshold",
                     "Default threshold (0.5) point",
                     callback=self._on_display_def_threshold_changed)

        gui.checkBox(box, self, "display_perf_line", "Show performance line",
                     callback=self._on_display_perf_line_changed)
        grid = QGridLayout()
        gui.indentedBox(box, orientation=grid)

        sp = gui.spin(box, self, "fp_cost", 1, 1000, 10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FP Cost:"), 0, 0)
        grid.addWidget(sp, 0, 1)

        sp = gui.spin(box, self, "fn_cost", 1, 1000, 10,
                      alignment=Qt.AlignRight,
                      callback=self._on_display_perf_line_changed)
        grid.addWidget(QLabel("FN Cost:"))
        grid.addWidget(sp, 1, 1)
        self.target_prior_sp = gui.spin(box, self, "target_prior", 1, 99,
                                        alignment=Qt.AlignRight,
                                        callback=self._on_target_prior_changed)
        self.target_prior_sp.setSuffix(" %")
        self.target_prior_sp.addAction(QAction("Auto", sp))
        grid.addWidget(QLabel("Prior probability:"))
        grid.addWidget(self.target_prior_sp, 2, 1)

        self.plotview = pg.GraphicsView(background="w")
        self.plotview.setFrameStyle(QFrame.StyledPanel)
        self.plotview.scene().sigMouseMoved.connect(self._on_mouse_moved)

        self.plot = pg.PlotItem(enableMenu=False)
        self.plot.setMouseEnabled(False, False)
        self.plot.hideButtons()

        pen = QPen(self.palette().color(QPalette.Text))

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("FP Rate (1-Specificity)")
        axis.setGrid(16)

        axis = self.plot.getAxis("left")
        axis.setTickFont(tickfont)
        axis.setPen(pen)
        axis.setLabel("TP Rate (Sensitivity)")
        axis.setGrid(16)

        self.plot.showGrid(True, True, alpha=0.1)
        self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0), padding=0.05)

        self.plotview.setCentralItem(self.plot)
        self.mainArea.layout().addWidget(self.plotview)
Пример #40
0
 def setActionIcon(action: QAction, name: str):
     icon = QIcon.fromTheme(name)
     if not icon.isNull():
         action.setIcon(icon)
Пример #41
0
 def add_zoom_actions(self, menu):
     zoom_in = QAction("Zoom in",
                       self,
                       triggered=self.plot.vb.set_mode_zooming)
     zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)])
     zoom_in.setShortcutContext(Qt.WidgetWithChildrenShortcut)
     self.addAction(zoom_in)
     if menu:
         menu.addAction(zoom_in)
     zoom_fit = QAction(
         "Zoom to fit",
         self,
         triggered=lambda x:
         (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning()))
     zoom_fit.setShortcuts(
         [Qt.Key_Backspace,
          QKeySequence(Qt.ControlModifier | Qt.Key_0)])
     zoom_fit.setShortcutContext(Qt.WidgetWithChildrenShortcut)
     self.addAction(zoom_fit)
     if menu:
         menu.addAction(zoom_fit)
Пример #42
0
class SelectTool(DataTool):
    cursor = Qt.ArrowCursor

    def __init__(self, parent, plot):
        super().__init__(parent, plot)
        self._item = None
        self._start_pos = None
        self._selection_rect = None
        self._mouse_dragging = False
        self._delete_action = QAction(
            "Delete", self, shortcutContext=Qt.WindowShortcut
        )
        self._delete_action.setShortcuts([QKeySequence.Delete,
                                          QKeySequence("Backspace")])
        self._delete_action.triggered.connect(self.delete)

    def setSelectionRect(self, rect):
        if self._selection_rect != rect:
            self._selection_rect = QRectF(rect)
            self._item.setRect(self._selection_rect)

    def selectionRect(self):
        return self._item.rect()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            pos = self.mapToPlot(event.pos())
            if self._item.isVisible():
                if self.selectionRect().contains(pos):
                    # Allow the event to propagate to the item.
                    event.setAccepted(False)
                    self._item.setCursor(Qt.ClosedHandCursor)
                    return False

            self._mouse_dragging = True

            self._start_pos = pos
            self._item.setVisible(True)
            self._plot.addItem(self._item)

            self.setSelectionRect(QRectF(pos, pos))
            event.accept()
            return True
        return super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            pos = self.mapToPlot(event.pos())
            self.setSelectionRect(QRectF(self._start_pos, pos).normalized())
            event.accept()
            return True
        return super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            pos = self.mapToPlot(event.pos())
            self.setSelectionRect(QRectF(self._start_pos, pos).normalized())
            event.accept()
            self.issueCommand.emit(SelectRegion(self.selectionRect()))
            self._item.setCursor(Qt.OpenHandCursor)
            self._mouse_dragging = False
            return True
        return super().mouseReleaseEvent(event)

    def activate(self):
        if self._item is None:
            self._item = _RectROI((0, 0), (0, 0), pen=(25, 25, 25))
            self._item.setAcceptedMouseButtons(Qt.LeftButton)
            self._item.setVisible(False)
            self._item.setCursor(Qt.OpenHandCursor)
            self._item.sigRegionChanged.connect(self._on_region_changed)
            self._item.sigRegionChangeStarted.connect(
                self._on_region_change_started)
            self._item.sigRegionChangeFinished.connect(
                self._on_region_change_finished)
            self._plot.addItem(self._item)
            self._mouse_dragging = False

        self._plot.addAction(self._delete_action)

    def deactivate(self):
        self._reset()
        self._plot.removeAction(self._delete_action)

    def _reset(self):
        self.setSelectionRect(QRectF())
        self._item.setVisible(False)
        self._mouse_dragging = False

    def delete(self):
        if not self._mouse_dragging and self._item.isVisible():
            self.issueCommand.emit(DeleteSelection())
            self._reset()

    def _on_region_changed(self):
        if not self._mouse_dragging:
            newrect = self._item.rect()
            delta = newrect.topLeft() - self._selection_rect.topLeft()
            self._selection_rect = newrect
            self.issueCommand.emit(MoveSelection(delta))

    def _on_region_change_started(self):
        if not self._mouse_dragging:
            self.editingStarted.emit()

    def _on_region_change_finished(self):
        if not self._mouse_dragging:
            self.editingFinished.emit()
Пример #43
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        form = self.layout().itemAt(0)
        assert isinstance(form, QFormLayout)

        #: A list model of discrete variable's values.
        self.values_model = itemmodels.PyListModel(
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
        )

        vlayout = QVBoxLayout(spacing=1, margin=0)
        self.values_edit = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed
        )
        self.values_edit.setItemDelegate(CategoriesEditDelegate(self))
        self.values_edit.setModel(self.values_model)
        self.values_model.dataChanged.connect(self.on_values_changed)

        self.values_edit.selectionModel().selectionChanged.connect(
            self.on_value_selection_changed)
        self.values_model.layoutChanged.connect(self.on_value_selection_changed)
        self.values_model.rowsMoved.connect(self.on_value_selection_changed)

        vlayout.addWidget(self.values_edit)
        hlayout = QHBoxLayout(spacing=1, margin=0)

        self.categories_action_group = group = QActionGroup(
            self, objectName="action-group-categories", enabled=False
        )
        self.move_value_up = QAction(
            "\N{UPWARDS ARROW}", group,
            toolTip="Move the selected item up.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier |
                                  Qt.Key_BracketLeft),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_up.triggered.connect(self.move_up)

        self.move_value_down = QAction(
            "\N{DOWNWARDS ARROW}", group,
            toolTip="Move the selected item down.",
            shortcut=QKeySequence(Qt.ControlModifier | Qt.AltModifier |
                                  Qt.Key_BracketRight),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.move_value_down.triggered.connect(self.move_down)

        self.add_new_item = QAction(
            "+", group,
            objectName="action-add-item",
            toolTip="Append a new item.",
            shortcut=QKeySequence(QKeySequence.New),
            shortcutContext=Qt.WidgetShortcut,
        )
        self.remove_item = QAction(
            "\N{MINUS SIGN}", group,
            objectName="action-remove-item",
            toolTip="Delete the selected item.",
            shortcut=QKeySequence(QKeySequence.Delete),
            shortcutContext=Qt.WidgetShortcut,
        )

        self.add_new_item.triggered.connect(self._add_category)
        self.remove_item.triggered.connect(self._remove_category)

        button1 = FixedSizeButton(
            self, defaultAction=self.move_value_up,
            accessibleName="Move up"
        )
        button2 = FixedSizeButton(
            self, defaultAction=self.move_value_down,
            accessibleName="Move down"
        )
        button3 = FixedSizeButton(
            self, defaultAction=self.add_new_item,
            accessibleName="Add"
        )
        button4 = FixedSizeButton(
            self, defaultAction=self.remove_item,
            accessibleName="Remove"
        )
        self.values_edit.addActions([self.move_value_up, self.move_value_down,
                                     self.add_new_item, self.remove_item])
        hlayout.addWidget(button1)
        hlayout.addWidget(button2)
        hlayout.addSpacing(3)
        hlayout.addWidget(button3)
        hlayout.addWidget(button4)

        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        form.insertRow(1, "Values:", vlayout)

        QWidget.setTabOrder(self.name_edit, self.values_edit)
        QWidget.setTabOrder(self.values_edit, button1)
        QWidget.setTabOrder(button1, button2)
        QWidget.setTabOrder(button2, button3)
        QWidget.setTabOrder(button3, button4)
Пример #44
0
class OWPaintData(OWWidget):
    TOOLS = [
        ("Brush", "Create multiple instances", AirBrushTool, _icon("brush.svg")),
        ("Put", "Put individual instances", PutInstanceTool, _icon("put.svg")),
        ("Select", "Select and move instances", SelectTool,
         _icon("select-transparent_42px.png")),
        ("Jitter", "Jitter instances", JitterTool, _icon("jitter.svg")),
        ("Magnet", "Attract multiple instances", MagnetTool, _icon("magnet.svg")),
        ("Clear", "Clear the plot", ClearTool, _icon("../../../icons/Dlg_clear.png"))
    ]

    name = "Paint Data"
    description = "Create data by painting data points on a plane."
    icon = "icons/PaintData.svg"
    priority = 15
    keywords = ["data", "paint", "create"]

    class Inputs:
        data = Input("Data", Orange.data.Table)

    class Outputs:
        data = Output("Data", Orange.data.Table)

    autocommit = Setting(True)
    table_name = Setting("Painted data")
    attr1 = Setting("x")
    attr2 = Setting("y")
    hasAttr2 = Setting(True)

    brushRadius = Setting(75)
    density = Setting(7)
    #: current data array (shape=(N, 3)) as presented on the output
    data = Setting(None, schema_only=True)
    labels = Setting(["C1", "C2"], schema_only=True)

    graph_name = "plot"

    class Warning(OWWidget.Warning):
        no_input_variables = Msg("Input data has no variables")
        continuous_target = Msg("Continuous target value can not be used.")
        sparse_not_supported = Msg("Sparse data is ignored.")

    class Information(OWWidget.Information):
        use_first_two = \
            Msg("Paint Data uses data from the first two attributes.")

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

        self.input_data = None
        self.input_classes = []
        self.input_colors = None
        self.input_has_attr2 = True
        self.current_tool = None
        self._selected_indices = None
        self._scatter_item = None
        #: A private data buffer (can be modified in place). `self.data` is
        #: a copy of this array (as seen when the `invalidate` method is
        #: called
        self.__buffer = None

        self.undo_stack = QUndoStack(self)

        self.class_model = ColoredListModel(
            self.labels, self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled |
            Qt.ItemIsEditable)

        self.class_model.dataChanged.connect(self._class_value_changed)
        self.class_model.rowsInserted.connect(self._class_count_changed)
        self.class_model.rowsRemoved.connect(self._class_count_changed)

        if not self.data:
            self.data = []
            self.__buffer = np.zeros((0, 3))
        elif isinstance(self.data, np.ndarray):
            self.__buffer = self.data.copy()
            self.data = self.data.tolist()
        else:
            self.__buffer = np.array(self.data)

        self.colors = colorpalette.ColorPaletteGenerator(
            len(colorpalette.DefaultRGBColors))
        self.tools_cache = {}

        self._init_ui()
        self.commit()

    def _init_ui(self):
        namesBox = gui.vBox(self.controlArea, "Names")

        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        gui.lineEdit(hbox, self, "attr1", "Variable X: ",
                     controlWidth=80, orientation=Qt.Horizontal,
                     callback=self._attr_name_changed)
        gui.separator(hbox, 21)
        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        attr2 = gui.lineEdit(hbox, self, "attr2", "Variable Y: ",
                             controlWidth=80, orientation=Qt.Horizontal,
                             callback=self._attr_name_changed)
        gui.separator(hbox)
        gui.checkBox(hbox, self, "hasAttr2", '', disables=attr2,
                     labelWidth=0,
                     callback=self.set_dimensions)
        gui.separator(namesBox)

        gui.widgetLabel(namesBox, "Labels")
        self.classValuesView = listView = gui.ListViewWithSizeHint(
            preferred_size=(-1, 30))
        listView.setModel(self.class_model)
        itemmodels.select_row(listView, 0)
        namesBox.layout().addWidget(listView)

        self.addClassLabel = QAction(
            "+", self,
            toolTip="Add new class label",
            triggered=self.add_new_class_label
        )

        self.removeClassLabel = QAction(
            unicodedata.lookup("MINUS SIGN"), self,
            toolTip="Remove selected class label",
            triggered=self.remove_selected_class_label
        )

        actionsWidget = itemmodels.ModelActionsWidget(
            [self.addClassLabel, self.removeClassLabel], self
        )
        actionsWidget.layout().addStretch(10)
        actionsWidget.layout().setSpacing(1)
        namesBox.layout().addWidget(actionsWidget)

        tBox = gui.vBox(self.controlArea, "Tools", addSpace=True)
        buttonBox = gui.hBox(tBox)
        toolsBox = gui.widgetBox(buttonBox, orientation=QGridLayout())

        self.toolActions = QActionGroup(self)
        self.toolActions.setExclusive(True)
        self.toolButtons = []

        for i, (name, tooltip, tool, icon) in enumerate(self.TOOLS):
            action = QAction(
                name, self,
                toolTip=tooltip,
                checkable=tool.checkable,
                icon=QIcon(icon),
            )
            action.triggered.connect(partial(self.set_current_tool, tool))

            button = QToolButton(
                iconSize=QSize(24, 24),
                toolButtonStyle=Qt.ToolButtonTextUnderIcon,
                sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.Fixed)
            )
            button.setDefaultAction(action)
            self.toolButtons.append((button, tool))

            toolsBox.layout().addWidget(button, i / 3, i % 3)
            self.toolActions.addAction(action)

        for column in range(3):
            toolsBox.layout().setColumnMinimumWidth(column, 10)
            toolsBox.layout().setColumnStretch(column, 1)

        undo = self.undo_stack.createUndoAction(self)
        redo = self.undo_stack.createRedoAction(self)

        undo.setShortcut(QKeySequence.Undo)
        redo.setShortcut(QKeySequence.Redo)

        self.addActions([undo, redo])
        self.undo_stack.indexChanged.connect(lambda _: self.invalidate())

        gui.separator(tBox)
        indBox = gui.indentedBox(tBox, sep=8)
        form = QFormLayout(
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )
        indBox.layout().addLayout(form)
        slider = gui.hSlider(
            indBox, self, "brushRadius", minValue=1, maxValue=100,
            createLabel=False
        )
        form.addRow("Radius:", slider)

        slider = gui.hSlider(
            indBox, self, "density", None, minValue=1, maxValue=100,
            createLabel=False
        )

        form.addRow("Intensity:", slider)
        self.btResetToInput = gui.button(
            tBox, self, "Reset to Input Data", self.reset_to_input)
        self.btResetToInput.setDisabled(True)

        gui.auto_commit(self.left_side, self, "autocommit",
                        "Send")

        # main area GUI
        viewbox = PaintViewBox(enableMouse=False)
        self.plotview = pg.PlotWidget(background="w", viewBox=viewbox)
        self.plotview.sizeHint = lambda: QSize(200, 100)  # Minimum size for 1-d painting
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.attr1)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.attr2)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)
        if not self.hasAttr2:
            self.plot.hideAxis('left')

        self.plot.hideButtons()
        self.plot.setXRange(0, 1, padding=0.01)

        self.mainArea.layout().addWidget(self.plotview)

        # enable brush tool
        self.toolActions.actions()[0].setChecked(True)
        self.set_current_tool(self.TOOLS[0][2])

        self.set_dimensions()

    def set_dimensions(self):
        if self.hasAttr2:
            self.plot.setYRange(0, 1, padding=0.01)
            self.plot.showAxis('left')
            self.plotview.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Minimum)
        else:
            self.plot.setYRange(-.5, .5, padding=0.01)
            self.plot.hideAxis('left')
            self.plotview.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum)
        self._replot()
        for button, tool in self.toolButtons:
            if tool.only2d:
                button.setDisabled(not self.hasAttr2)

    @Inputs.data
    def set_data(self, data):
        """Set the input_data and call reset_to_input"""
        def _check_and_set_data(data):
            self.clear_messages()
            if data and data.is_sparse():
                self.Warning.sparse_not_supported()
                return False
            if data is not None and len(data):
                if not data.domain.attributes:
                    self.Warning.no_input_variables()
                    data = None
                elif len(data.domain.attributes) > 2:
                    self.Information.use_first_two()
            self.input_data = data
            self.btResetToInput.setDisabled(data is None)
            return data is not None and len(data)

        if not _check_and_set_data(data):
            return

        X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
        try:
            y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
        except StopIteration:
            if data.domain.class_vars:
                self.Warning.continuous_target()
            self.input_classes = ["C1"]
            self.input_colors = None
            y = np.zeros(len(data))
        else:
            self.input_classes = y.values
            self.input_colors = y.colors

            y = data[:, y].Y

        self.input_has_attr2 = len(data.domain.attributes) >= 2
        if not self.input_has_attr2:
            self.input_data = np.column_stack((X, np.zeros(len(data)), y))
        else:
            self.input_data = np.column_stack((X, y))
        self.reset_to_input()
        self.unconditional_commit()

    def reset_to_input(self):
        """Reset the painting to input data if present."""
        if self.input_data is None:
            return
        self.undo_stack.clear()

        index = self.selected_class_label()
        if self.input_colors is not None:
            colors = self.input_colors
        else:
            colors = colorpalette.DefaultRGBColors
        palette = colorpalette.ColorPaletteGenerator(
            number_of_colors=len(colors), rgb_colors=colors)
        self.colors = palette
        self.class_model.colors = palette
        self.class_model[:] = self.input_classes

        newindex = min(max(index, 0), len(self.class_model) - 1)
        itemmodels.select_row(self.classValuesView, newindex)

        self.data = self.input_data.tolist()
        self.__buffer = self.input_data.copy()

        prev_attr2 = self.hasAttr2
        self.hasAttr2 = self.input_has_attr2
        if prev_attr2 != self.hasAttr2:
            self.set_dimensions()
        else:  # set_dimensions already calls _replot, no need to call it again
            self._replot()

    def add_new_class_label(self, undoable=True):

        newlabel = next(label for label in namegen('C', 1)
                        if label not in self.class_model)

        command = SimpleUndoCommand(
            lambda: self.class_model.append(newlabel),
            lambda: self.class_model.__delitem__(-1)
        )
        if undoable:
            self.undo_stack.push(command)
        else:
            command.redo()

    def remove_selected_class_label(self):
        index = self.selected_class_label()

        if index is None:
            return

        label = self.class_model[index]
        mask = self.__buffer[:, 2] == index
        move_mask = self.__buffer[~mask][:, 2] > index

        self.undo_stack.beginMacro("Delete class label")
        self.undo_stack.push(UndoCommand(DeleteIndices(mask), self))
        self.undo_stack.push(UndoCommand(Move((move_mask, 2), -1), self))
        self.undo_stack.push(
            SimpleUndoCommand(lambda: self.class_model.__delitem__(index),
                              lambda: self.class_model.insert(index, label)))
        self.undo_stack.endMacro()

        newindex = min(max(index - 1, 0), len(self.class_model) - 1)
        itemmodels.select_row(self.classValuesView, newindex)

    def _class_count_changed(self):
        self.labels = list(self.class_model)
        self.removeClassLabel.setEnabled(len(self.class_model) > 1)
        self.addClassLabel.setEnabled(
            len(self.class_model) < self.colors.number_of_colors)
        if self.selected_class_label() is None:
            itemmodels.select_row(self.classValuesView, 0)

    def _class_value_changed(self, index, _):
        index = index.row()
        newvalue = self.class_model[index]
        oldvalue = self.labels[index]
        if newvalue != oldvalue:
            self.labels[index] = newvalue
#             command = Command(
#                 lambda: self.class_model.__setitem__(index, newvalue),
#                 lambda: self.class_model.__setitem__(index, oldvalue),
#             )
#             self.undo_stack.push(command)

    def selected_class_label(self):
        rows = self.classValuesView.selectedIndexes()
        if rows:
            return rows[0].row()
        return None

    def set_current_tool(self, tool):
        prev_tool = self.current_tool.__class__

        if self.current_tool is not None:
            self.current_tool.deactivate()
            self.current_tool.editingStarted.disconnect(
                self._on_editing_started)
            self.current_tool.editingFinished.disconnect(
                self._on_editing_finished)
            self.current_tool = None
            self.plot.getViewBox().tool = None

        if tool not in self.tools_cache:
            newtool = tool(self, self.plot)
            self.tools_cache[tool] = newtool
            newtool.issueCommand.connect(self._add_command)

        self._selected_region = QRectF()
        self.current_tool = tool = self.tools_cache[tool]
        self.plot.getViewBox().tool = tool
        tool.editingStarted.connect(self._on_editing_started)
        tool.editingFinished.connect(self._on_editing_finished)
        tool.activate()

        if not tool.checkable:
            self.set_current_tool(prev_tool)

    def _on_editing_started(self):
        self.undo_stack.beginMacro("macro")

    def _on_editing_finished(self):
        self.undo_stack.endMacro()

    def execute(self, command):
        if isinstance(command, (Append, DeleteIndices, Insert, Move)):
            if isinstance(command, (DeleteIndices, Insert)):
                self._selected_indices = None

                if isinstance(self.current_tool, SelectTool):
                    self.current_tool._reset()

            self.__buffer, undo = transform(command, self.__buffer)
            self._replot()
            return undo
        else:
            assert False, "Non normalized command"

    def _add_command(self, cmd):
        name = "Name"

        if (not self.hasAttr2 and
                isinstance(cmd, (Move, MoveSelection, Jitter, Magnet))):
            # tool only supported if both x and y are enabled
            return

        if isinstance(cmd, Append):
            cls = self.selected_class_label()
            points = np.array([(p.x(), p.y() if self.hasAttr2 else 0, cls)
                               for p in cmd.points])
            self.undo_stack.push(UndoCommand(Append(points), self, text=name))
        elif isinstance(cmd, Move):
            self.undo_stack.push(UndoCommand(cmd, self, text=name))
        elif isinstance(cmd, SelectRegion):
            indices = [i for i, (x, y) in enumerate(self.__buffer[:, :2])
                       if cmd.region.contains(QPointF(x, y))]
            indices = np.array(indices, dtype=int)
            self._selected_indices = indices
        elif isinstance(cmd, DeleteSelection):
            indices = self._selected_indices
            if indices is not None and indices.size:
                self.undo_stack.push(
                    UndoCommand(DeleteIndices(indices), self, text="Delete")
                )
        elif isinstance(cmd, MoveSelection):
            indices = self._selected_indices
            if indices is not None and indices.size:
                self.undo_stack.push(
                    UndoCommand(
                        Move((self._selected_indices, slice(0, 2)),
                             np.array([cmd.delta.x(), cmd.delta.y()])),
                        self, text="Move")
                )
        elif isinstance(cmd, DeleteIndices):
            self.undo_stack.push(UndoCommand(cmd, self, text="Delete"))
        elif isinstance(cmd, Insert):
            self.undo_stack.push(UndoCommand(cmd, self))
        elif isinstance(cmd, AirBrush):
            data = create_data(cmd.pos.x(), cmd.pos.y(),
                               self.brushRadius / 1000,
                               int(1 + self.density / 20), cmd.rstate)
            self._add_command(Append([QPointF(*p) for p in zip(*data.T)]))
        elif isinstance(cmd, Jitter):
            point = np.array([cmd.pos.x(), cmd.pos.y()])
            delta = - apply_jitter(self.__buffer[:, :2], point,
                                   self.density / 100.0, 0, cmd.rstate)
            self._add_command(Move((..., slice(0, 2)), delta))
        elif isinstance(cmd, Magnet):
            point = np.array([cmd.pos.x(), cmd.pos.y()])
            delta = - apply_attractor(self.__buffer[:, :2], point,
                                      self.density / 100.0, 0)
            self._add_command(Move((..., slice(0, 2)), delta))
        else:
            assert False, "unreachable"

    def _replot(self):
        def pen(color):
            pen = QPen(color, 1)
            pen.setCosmetic(True)
            return pen

        if self._scatter_item is not None:
            self.plot.removeItem(self._scatter_item)
            self._scatter_item = None

        x = self.__buffer[:, 0].copy()
        if self.hasAttr2:
            y = self.__buffer[:, 1].copy()
        else:
            y = np.zeros(self.__buffer.shape[0])

        colors = self.colors[self.__buffer[:, 2]]
        pens = [pen(c) for c in colors]
        self._scatter_item = pg.ScatterPlotItem(
            x, y, symbol="+", pen=pens
        )
        self.plot.addItem(self._scatter_item)

    def _attr_name_changed(self):
        self.plot.getAxis("bottom").setLabel(self.attr1)
        self.plot.getAxis("left").setLabel(self.attr2)
        self.invalidate()

    def invalidate(self):
        self.data = self.__buffer.tolist()
        self.commit()

    def commit(self):
        data = np.array(self.data)
        if len(data) == 0:
            self.Outputs.data.send(None)
            return
        if self.hasAttr2:
            X, Y = data[:, :2], data[:, 2]
            attrs = (Orange.data.ContinuousVariable(self.attr1),
                     Orange.data.ContinuousVariable(self.attr2))
        else:
            X, Y = data[:, np.newaxis, 0], data[:, 2]
            attrs = (Orange.data.ContinuousVariable(self.attr1),)
        if len(np.unique(Y)) >= 2:
            domain = Orange.data.Domain(
                attrs,
                Orange.data.DiscreteVariable(
                    "Class", values=list(self.class_model))
            )
            data = Orange.data.Table.from_numpy(domain, X, Y)
        else:
            domain = Orange.data.Domain(attrs)
            data = Orange.data.Table.from_numpy(domain, X)
        data.name = self.table_name
        self.Outputs.data.send(data)

    def sizeHint(self):
        sh = super().sizeHint()
        return sh.expandedTo(QSize(570, 690))

    def onDeleteWidget(self):
        self.plot.clear()

    def send_report(self):
        if self.data is None:
            return
        settings = []
        if self.attr1 != "x" or self.attr2 != "y":
            settings += [("Axis x", self.attr1), ("Axis y", self.attr2)]
        settings += [("Number of points", len(self.data))]
        self.report_items("Painted data", settings)
        self.report_plot()
Пример #45
0
    class Frame(QDockWidget):
        """
        Widget frame with a handle.
        """
        closeRequested = Signal()

        def __init__(self, parent=None, widget=None, title=None, **kwargs):

            super().__init__(parent, **kwargs)
            self.setFeatures(QDockWidget.DockWidgetClosable)
            self.setAllowedAreas(Qt.NoDockWidgetArea)

            self.__title = ""
            self.__icon = ""
            self.__focusframe = None

            self.__deleteaction = QAction("Remove",
                                          self,
                                          shortcut=QKeySequence.Delete,
                                          enabled=False,
                                          triggered=self.closeRequested)
            self.addAction(self.__deleteaction)

            if widget is not None:
                self.setWidget(widget)
            self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

            if title:
                self.setTitle(title)

            self.setFocusPolicy(Qt.ClickFocus | Qt.TabFocus)

        def setTitle(self, title):
            if self.__title != title:
                self.__title = title
                self.setWindowTitle(title)
                self.update()

        def setIcon(self, icon):
            icon = QIcon(icon)
            if self.__icon != icon:
                self.__icon = icon
                self.setWindowIcon(icon)
                self.update()

        def paintEvent(self, event):
            super().paintEvent(event)
            painter = QStylePainter(self)
            opt = QStyleOptionFrame()
            opt.initFrom(self)
            painter.drawPrimitive(QStyle.PE_FrameDockWidget, opt)
            painter.end()

        def focusInEvent(self, event):
            event.accept()
            self.__focusframe = QFocusFrame(self)
            self.__focusframe.setWidget(self)
            self.__deleteaction.setEnabled(True)

        def focusOutEvent(self, event):
            event.accept()
            if self.__focusframe is not None:
                self.__focusframe.deleteLater()
                self.__focusframe = None
            self.__deleteaction.setEnabled(False)

        def closeEvent(self, event):
            super().closeEvent(event)
            event.ignore()
            self.closeRequested.emit()
Пример #46
0
    def _init_ui(self):
        namesBox = gui.vBox(self.controlArea, "Names")

        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        gui.lineEdit(hbox, self, "attr1", "Variable X: ",
                     controlWidth=80, orientation=Qt.Horizontal,
                     callback=self._attr_name_changed)
        gui.separator(hbox, 21)
        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        attr2 = gui.lineEdit(hbox, self, "attr2", "Variable Y: ",
                             controlWidth=80, orientation=Qt.Horizontal,
                             callback=self._attr_name_changed)
        gui.separator(hbox)
        gui.checkBox(hbox, self, "hasAttr2", '', disables=attr2,
                     labelWidth=0,
                     callback=self.set_dimensions)
        gui.separator(namesBox)

        gui.widgetLabel(namesBox, "Labels")
        self.classValuesView = listView = gui.ListViewWithSizeHint(
            preferred_size=(-1, 30))
        listView.setModel(self.class_model)
        itemmodels.select_row(listView, 0)
        namesBox.layout().addWidget(listView)

        self.addClassLabel = QAction(
            "+", self,
            toolTip="Add new class label",
            triggered=self.add_new_class_label
        )

        self.removeClassLabel = QAction(
            unicodedata.lookup("MINUS SIGN"), self,
            toolTip="Remove selected class label",
            triggered=self.remove_selected_class_label
        )

        actionsWidget = itemmodels.ModelActionsWidget(
            [self.addClassLabel, self.removeClassLabel], self
        )
        actionsWidget.layout().addStretch(10)
        actionsWidget.layout().setSpacing(1)
        namesBox.layout().addWidget(actionsWidget)

        tBox = gui.vBox(self.controlArea, "Tools", addSpace=True)
        buttonBox = gui.hBox(tBox)
        toolsBox = gui.widgetBox(buttonBox, orientation=QGridLayout())

        self.toolActions = QActionGroup(self)
        self.toolActions.setExclusive(True)
        self.toolButtons = []

        for i, (name, tooltip, tool, icon) in enumerate(self.TOOLS):
            action = QAction(
                name, self,
                toolTip=tooltip,
                checkable=tool.checkable,
                icon=QIcon(icon),
            )
            action.triggered.connect(partial(self.set_current_tool, tool))

            button = QToolButton(
                iconSize=QSize(24, 24),
                toolButtonStyle=Qt.ToolButtonTextUnderIcon,
                sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.Fixed)
            )
            button.setDefaultAction(action)
            self.toolButtons.append((button, tool))

            toolsBox.layout().addWidget(button, i / 3, i % 3)
            self.toolActions.addAction(action)

        for column in range(3):
            toolsBox.layout().setColumnMinimumWidth(column, 10)
            toolsBox.layout().setColumnStretch(column, 1)

        undo = self.undo_stack.createUndoAction(self)
        redo = self.undo_stack.createRedoAction(self)

        undo.setShortcut(QKeySequence.Undo)
        redo.setShortcut(QKeySequence.Redo)

        self.addActions([undo, redo])
        self.undo_stack.indexChanged.connect(lambda _: self.invalidate())

        gui.separator(tBox)
        indBox = gui.indentedBox(tBox, sep=8)
        form = QFormLayout(
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )
        indBox.layout().addLayout(form)
        slider = gui.hSlider(
            indBox, self, "brushRadius", minValue=1, maxValue=100,
            createLabel=False
        )
        form.addRow("Radius:", slider)

        slider = gui.hSlider(
            indBox, self, "density", None, minValue=1, maxValue=100,
            createLabel=False
        )

        form.addRow("Intensity:", slider)
        self.btResetToInput = gui.button(
            tBox, self, "Reset to Input Data", self.reset_to_input)
        self.btResetToInput.setDisabled(True)

        gui.auto_commit(self.left_side, self, "autocommit",
                        "Send")

        # main area GUI
        viewbox = PaintViewBox(enableMouse=False)
        self.plotview = pg.PlotWidget(background="w", viewBox=viewbox)
        self.plotview.sizeHint = lambda: QSize(200, 100)  # Minimum size for 1-d painting
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.attr1)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.attr2)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)
        if not self.hasAttr2:
            self.plot.hideAxis('left')

        self.plot.hideButtons()
        self.plot.setXRange(0, 1, padding=0.01)

        self.mainArea.layout().addWidget(self.plotview)

        # enable brush tool
        self.toolActions.actions()[0].setChecked(True)
        self.set_current_tool(self.TOOLS[0][2])

        self.set_dimensions()
Пример #47
0
    def __init__(self):
        super().__init__()
        self.libraryListSource = []

        for name in self.signal_names:
            setattr(self, name, {})

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " +
            "<li>".join(map("in_{0}, in_{0}s".format, self.signal_names)) +
            "</ul></p><p>Output variables:<ul><li>" +
            "<li>".join(map("out_{0}".format, self.signal_names)) +
            "</ul></p>")

        self.libraryList = itemmodels.PyListModel(
            [],
            self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored, QSizePolicy.Preferred))
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged)
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        restore_saved = QAction("Undo Changes to Selected Script", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)
        restore_saved.triggered.connect(self.restoreSaved)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        menu.addAction(restore_saved)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.button(self.controlArea,
                                         self,
                                         'Run',
                                         callback=self.commit)

        run = QAction("Run script",
                      self,
                      triggered=self.commit,
                      shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_R))
        self.addAction(run)

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python Script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)
        self.splitCanvas.setSizes([2, 1])
        self.setAcceptDrops(True)
        self.controlArea.layout().addStretch(10)

        self._restoreState()
        self.settingsAboutToBePacked.connect(self._saveState)
    def __init__(self):
        super().__init__()

        self.matrix = None
        self.items = None
        self.linkmatrix = None
        self.root = None
        self._displayed_root = None
        self.cutoff_height = 0.0

        self._set_input_summary(None)
        self._set_output_summary(None)

        gui.comboBox(
            self.controlArea, self, "linkage", items=LINKAGE, box="Linkage",
            callback=self._invalidate_clustering)

        model = itemmodels.VariableListModel()
        model[:] = self.basic_annotations

        box = gui.widgetBox(self.controlArea, "Annotations")
        self.label_cb = cb = combobox.ComboBoxSearch(
            minimumContentsLength=14,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon
        )
        cb.setModel(model)
        cb.setCurrentIndex(cb.findData(self.annotation, Qt.EditRole))

        def on_annotation_activated():
            self.annotation = cb.currentData(Qt.EditRole)
            self._update_labels()
        cb.activated.connect(on_annotation_activated)

        def on_annotation_changed(value):
            cb.setCurrentIndex(cb.findData(value, Qt.EditRole))
        self.connect_control("annotation", on_annotation_changed)

        box.layout().addWidget(self.label_cb)

        box = gui.radioButtons(
            self.controlArea, self, "pruning", box="Pruning",
            callback=self._invalidate_pruning)
        grid = QGridLayout()
        box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(box, "None", addToLayout=False),
            0, 0
        )
        self.max_depth_spin = gui.spin(
            box, self, "max_depth", minv=1, maxv=100,
            callback=self._invalidate_pruning,
            keyboardTracking=False, addToLayout=False
        )

        grid.addWidget(
            gui.appendRadioButton(box, "Max depth:", addToLayout=False),
            1, 0)
        grid.addWidget(self.max_depth_spin, 1, 1)

        self.selection_box = gui.radioButtons(
            self.controlArea, self, "selection_method",
            box="Selection",
            callback=self._selection_method_changed)

        grid = QGridLayout()
        self.selection_box.layout().addLayout(grid)
        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Manual", addToLayout=False),
            0, 0
        )
        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Height ratio:", addToLayout=False),
            1, 0
        )
        self.cut_ratio_spin = gui.spin(
            self.selection_box, self, "cut_ratio", 0, 100, step=1e-1,
            spinType=float, callback=self._selection_method_changed,
            addToLayout=False
        )
        self.cut_ratio_spin.setSuffix("%")

        grid.addWidget(self.cut_ratio_spin, 1, 1)

        grid.addWidget(
            gui.appendRadioButton(
                self.selection_box, "Top N:", addToLayout=False),
            2, 0
        )
        self.top_n_spin = gui.spin(self.selection_box, self, "top_n", 1, 20,
                                   callback=self._selection_method_changed,
                                   addToLayout=False)
        grid.addWidget(self.top_n_spin, 2, 1)

        self.zoom_slider = gui.hSlider(
            self.controlArea, self, "zoom_factor", box="Zoom",
            minValue=-6, maxValue=3, step=1, ticks=True, createLabel=False,
            callback=self.__update_font_scale)

        zoom_in = QAction(
            "Zoom in", self, shortcut=QKeySequence.ZoomIn,
            triggered=self.__zoom_in
        )
        zoom_out = QAction(
            "Zoom out", self, shortcut=QKeySequence.ZoomOut,
            triggered=self.__zoom_out
        )
        zoom_reset = QAction(
            "Reset zoom", self,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
            triggered=self.__zoom_reset
        )
        self.addActions([zoom_in, zoom_out, zoom_reset])

        self.controlArea.layout().addStretch()

        gui.auto_send(self.buttonsArea, self, "autocommit")

        self.scene = QGraphicsScene(self)
        self.view = StickyGraphicsView(
            self.scene,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff,
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            alignment=Qt.AlignLeft | Qt.AlignVCenter
        )
        self.mainArea.layout().setSpacing(1)
        self.mainArea.layout().addWidget(self.view)

        def axis_view(orientation):
            ax = AxisItem(orientation=orientation, maxTickLength=7)
            ax.mousePressed.connect(self._activate_cut_line)
            ax.mouseMoved.connect(self._activate_cut_line)
            ax.mouseReleased.connect(self._activate_cut_line)
            ax.setRange(1.0, 0.0)
            return ax

        self.top_axis = axis_view("top")
        self.bottom_axis = axis_view("bottom")

        self._main_graphics = QGraphicsWidget()
        scenelayout = QGraphicsGridLayout()
        scenelayout.setHorizontalSpacing(10)
        scenelayout.setVerticalSpacing(10)

        self._main_graphics.setLayout(scenelayout)
        self.scene.addItem(self._main_graphics)

        self.dendrogram = DendrogramWidget()
        self.dendrogram.setSizePolicy(QSizePolicy.MinimumExpanding,
                                      QSizePolicy.MinimumExpanding)
        self.dendrogram.selectionChanged.connect(self._invalidate_output)
        self.dendrogram.selectionEdited.connect(self._selection_edited)

        self.labels = TextListWidget()
        self.labels.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        self.labels.setAlignment(Qt.AlignLeft)
        self.labels.setMaximumWidth(200)

        scenelayout.addItem(self.top_axis, 0, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.dendrogram, 1, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.labels, 1, 1,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        scenelayout.addItem(self.bottom_axis, 2, 0,
                            alignment=Qt.AlignLeft | Qt.AlignVCenter)
        self.view.viewport().installEventFilter(self)
        self._main_graphics.installEventFilter(self)

        self.top_axis.setZValue(self.dendrogram.zValue() + 10)
        self.bottom_axis.setZValue(self.dendrogram.zValue() + 10)
        self.cut_line = SliderLine(self.top_axis,
                                   orientation=Qt.Horizontal)
        self.cut_line.valueChanged.connect(self._dendrogram_slider_changed)
        self.dendrogram.geometryChanged.connect(self._dendrogram_geom_changed)
        self._set_cut_line_visible(self.selection_method == 1)
        self.__update_font_scale()
Пример #49
0
class SelectTool(DataTool):
    cursor = Qt.ArrowCursor

    def __init__(self, parent, plot):
        super().__init__(parent, plot)
        self._item = None
        self._start_pos = None
        self._selection_rect = None
        self._mouse_dragging = False
        self._delete_action = QAction(
            "Delete", self, shortcutContext=Qt.WindowShortcut
        )
        self._delete_action.setShortcuts([QKeySequence.Delete,
                                          QKeySequence("Backspace")])
        self._delete_action.triggered.connect(self.delete)

    def setSelectionRect(self, rect):
        if self._selection_rect != rect:
            self._selection_rect = QRectF(rect)
            self._item.setRect(self._selection_rect)

    def selectionRect(self):
        return self._item.rect()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            pos = self.mapToPlot(event.pos())
            if self._item.isVisible():
                if self.selectionRect().contains(pos):
                    # Allow the event to propagate to the item.
                    event.setAccepted(False)
                    self._item.setCursor(Qt.ClosedHandCursor)
                    return False

            self._mouse_dragging = True

            self._start_pos = pos
            self._item.setVisible(True)
            self._plot.addItem(self._item)

            self.setSelectionRect(QRectF(pos, pos))
            event.accept()
            return True
        else:
            return super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.LeftButton:
            pos = self.mapToPlot(event.pos())
            self.setSelectionRect(QRectF(self._start_pos, pos).normalized())
            event.accept()
            return True
        else:
            return super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            pos = self.mapToPlot(event.pos())
            self.setSelectionRect(QRectF(self._start_pos, pos).normalized())
            event.accept()
            self.issueCommand.emit(SelectRegion(self.selectionRect()))
            self._item.setCursor(Qt.OpenHandCursor)
            self._mouse_dragging = False
            return True
        else:
            return super().mouseReleaseEvent(event)

    def activate(self):
        if self._item is None:
            self._item = _RectROI((0, 0), (0, 0), pen=(25, 25, 25))
            self._item.setAcceptedMouseButtons(Qt.LeftButton)
            self._item.setVisible(False)
            self._item.setCursor(Qt.OpenHandCursor)
            self._item.sigRegionChanged.connect(self._on_region_changed)
            self._item.sigRegionChangeStarted.connect(
                self._on_region_change_started)
            self._item.sigRegionChangeFinished.connect(
                self._on_region_change_finished)
            self._plot.addItem(self._item)
            self._mouse_dragging = False

        self._plot.addAction(self._delete_action)

    def deactivate(self):
        self._reset()
        self._plot.removeAction(self._delete_action)

    def _reset(self):
        self.setSelectionRect(QRectF())
        self._item.setVisible(False)
        self._mouse_dragging = False

    def delete(self):
        if not self._mouse_dragging and self._item.isVisible():
            self.issueCommand.emit(DeleteSelection())
            self._reset()

    def _on_region_changed(self):
        if not self._mouse_dragging:
            newrect = self._item.rect()
            delta = newrect.topLeft() - self._selection_rect.topLeft()
            self._selection_rect = newrect
            self.issueCommand.emit(MoveSelection(delta))

    def _on_region_change_started(self):
        if not self._mouse_dragging:
            self.editingStarted.emit()

    def _on_region_change_finished(self):
        if not self._mouse_dragging:
            self.editingFinished.emit()
Пример #50
0
class OWHeatMap(widget.OWWidget):
    name = "Heat Map"
    description = "Plot a data matrix heatmap."
    icon = "icons/Heatmap.svg"
    priority = 260
    keywords = []

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

    class Outputs:
        selected_data = Output("Selected Data", Table, default=True)
        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)

    settings_version = 3

    settingsHandler = settings.DomainContextHandler()

    # Disable clustering for inputs bigger than this
    MaxClustering = 25000
    # Disable cluster leaf ordering for inputs bigger than this
    MaxOrderedClustering = 1000

    threshold_low = settings.Setting(0.0)
    threshold_high = settings.Setting(1.0)

    merge_kmeans = settings.Setting(False)
    merge_kmeans_k = settings.Setting(50)

    # Display column with averages
    averages: bool = settings.Setting(True)
    # Display legend
    legend: bool = settings.Setting(True)
    # Annotations
    #: text row annotation (row names)
    annotation_var = settings.ContextSetting(None)
    #: color row annotation
    annotation_color_var = settings.ContextSetting(None)
    # Discrete variable used to split that data/heatmaps (vertically)
    split_by_var = settings.ContextSetting(None)
    # Selected row/column clustering method (name)
    col_clustering_method: str = settings.Setting(Clustering.None_.name)
    row_clustering_method: str = settings.Setting(Clustering.None_.name)

    palette_name = settings.Setting(colorpalettes.DefaultContinuousPaletteName)
    column_label_pos: int = settings.Setting(1)
    selected_rows: List[int] = settings.Setting(None, schema_only=True)

    auto_commit = settings.Setting(True)

    graph_name = "scene"

    left_side_scrolling = True

    class Information(widget.OWWidget.Information):
        sampled = Msg("Data has been sampled")
        discrete_ignored = Msg("{} categorical feature{} ignored")
        row_clust = Msg("{}")
        col_clust = Msg("{}")
        sparse_densified = Msg("Showing this data may require a lot of memory")

    class Error(widget.OWWidget.Error):
        no_continuous = Msg("No numeric features")
        not_enough_features = Msg("Not enough features for column clustering")
        not_enough_instances = Msg("Not enough instances for clustering")
        not_enough_instances_k_means = Msg(
            "Not enough instances for k-means merging")
        not_enough_memory = Msg("Not enough memory to show this data")

    class Warning(widget.OWWidget.Warning):
        empty_clusters = Msg("Empty clusters were removed")

    def __init__(self):
        super().__init__()
        self.__pending_selection = self.selected_rows

        # A kingdom for a save_state/restore_state
        self.col_clustering = enum_get(
            Clustering, self.col_clustering_method, Clustering.None_)
        self.row_clustering = enum_get(
            Clustering, self.row_clustering_method, Clustering.None_)

        @self.settingsAboutToBePacked.connect
        def _():
            self.col_clustering_method = self.col_clustering.name
            self.row_clustering_method = self.row_clustering.name

        self.keep_aspect = False

        #: The original data with all features (retained to
        #: preserve the domain on the output)
        self.input_data = None
        #: The effective data striped of discrete features, and often
        #: merged using k-means
        self.data = None
        self.effective_data = None
        #: kmeans model used to merge rows of input_data
        self.kmeans_model = None
        #: merge indices derived from kmeans
        #: a list (len==k) of int ndarray where the i-th item contains
        #: the indices which merge the input_data into the heatmap row i
        self.merge_indices = None
        self.parts: Optional[Parts] = None
        self.__rows_cache = {}
        self.__columns_cache = {}

        self.info.set_input_summary(self.info.NoInput)
        self.info.set_output_summary(self.info.NoOutput)

        # GUI definition
        colorbox = gui.vBox(self.controlArea, "Color")

        self.color_map_widget = cmw = ColorGradientSelection(
            thresholds=(self.threshold_low, self.threshold_high),
        )
        model = itemmodels.ContinuousPalettesModel(parent=self)
        cmw.setModel(model)
        idx = cmw.findData(self.palette_name, model.KeyRole)
        if idx != -1:
            cmw.setCurrentIndex(idx)

        cmw.activated.connect(self.update_color_schema)

        def _set_thresholds(low, high):
            self.threshold_low, self.threshold_high = low, high
            self.update_color_schema()
        cmw.thresholdsChanged.connect(_set_thresholds)
        colorbox.layout().addWidget(self.color_map_widget)

        mergebox = gui.vBox(self.controlArea, "Merge",)
        gui.checkBox(mergebox, self, "merge_kmeans", "Merge by k-means",
                     callback=self.__update_row_clustering)
        ibox = gui.indentedBox(mergebox)
        gui.spin(ibox, self, "merge_kmeans_k", minv=5, maxv=500,
                 label="Clusters:", keyboardTracking=False,
                 callbackOnReturn=True, callback=self.update_merge)

        cluster_box = gui.vBox(self.controlArea, "Clustering")
        # Row clustering
        self.row_cluster_cb = cb = ComboBox()
        cb.setModel(create_list_model(ClusteringModelData, self))
        cbselect(cb, self.row_clustering, ClusteringRole)
        self.connect_control(
            "row_clustering",
            lambda value, cb=cb: cbselect(cb, value, ClusteringRole)
        )
        @cb.activated.connect
        def _(idx, cb=cb):
            self.set_row_clustering(cb.itemData(idx, ClusteringRole))

        # Column clustering
        self.col_cluster_cb = cb = ComboBox()
        cb.setModel(create_list_model(ClusteringModelData, self))
        cbselect(cb, self.col_clustering, ClusteringRole)
        self.connect_control(
            "col_clustering",
            lambda value, cb=cb: cbselect(cb, value, ClusteringRole)
        )
        @cb.activated.connect
        def _(idx, cb=cb):
            self.set_col_clustering(cb.itemData(idx, ClusteringRole))

        form = QFormLayout(
            labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow,
        )
        form.addRow("Rows:", self.row_cluster_cb)
        form.addRow("Columns:", self.col_cluster_cb)
        cluster_box.layout().addLayout(form)
        box = gui.vBox(self.controlArea, "Split By")

        self.row_split_model = DomainModel(
            placeholder="(None)",
            valid_types=(Orange.data.DiscreteVariable,),
            parent=self,
        )
        self.row_split_cb = cb = ComboBoxSearch(
            enabled=not self.merge_kmeans,
            sizeAdjustPolicy=ComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=14,
            toolTip="Split the heatmap vertically by a categorical column"
        )
        self.row_split_cb.setModel(self.row_split_model)
        self.connect_control(
            "split_by_var", lambda value, cb=cb: cbselect(cb, value)
        )
        self.connect_control(
            "merge_kmeans", self.row_split_cb.setDisabled
        )
        self.split_by_var = None

        self.row_split_cb.activated.connect(
            self.__on_split_rows_activated
        )
        box.layout().addWidget(self.row_split_cb)

        box = gui.vBox(self.controlArea, 'Annotation && Legends')

        gui.checkBox(box, self, 'legend', 'Show legend',
                     callback=self.update_legend)

        gui.checkBox(box, self, 'averages', 'Stripes with averages',
                     callback=self.update_averages_stripe)
        annotbox = QGroupBox("Row Annotations", flat=True)
        form = QFormLayout(
            annotbox,
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )
        self.annotation_model = DomainModel(placeholder="(None)")
        self.annotation_text_cb = ComboBoxSearch(
            minimumContentsLength=12,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength
        )
        self.annotation_text_cb.setModel(self.annotation_model)
        self.annotation_text_cb.activated.connect(self.set_annotation_var)
        self.connect_control("annotation_var", self.annotation_var_changed)

        self.row_side_color_model = DomainModel(
            order=(DomainModel.CLASSES, DomainModel.Separator,
                   DomainModel.METAS),
            placeholder="(None)", valid_types=DomainModel.PRIMITIVE,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled,
            parent=self,
        )
        self.row_side_color_cb = ComboBoxSearch(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
            minimumContentsLength=12
        )
        self.row_side_color_cb.setModel(self.row_side_color_model)
        self.row_side_color_cb.activated.connect(self.set_annotation_color_var)
        self.connect_control("annotation_color_var", self.annotation_color_var_changed)
        form.addRow("Text", self.annotation_text_cb)
        form.addRow("Color", self.row_side_color_cb)
        box.layout().addWidget(annotbox)
        posbox = gui.vBox(box, "Column Labels Position", addSpace=False)
        posbox.setFlat(True)
        cb = gui.comboBox(
            posbox, self, "column_label_pos",
            callback=self.update_column_annotations)
        cb.setModel(create_list_model(ColumnLabelsPosData, parent=self))
        cb.setCurrentIndex(self.column_label_pos)
        gui.checkBox(self.controlArea, self, "keep_aspect",
                     "Keep aspect ratio", box="Resize",
                     callback=self.__aspect_mode_changed)

        gui.rubber(self.controlArea)
        gui.auto_send(self.controlArea, self, "auto_commit")

        # Scene with heatmap
        class HeatmapScene(QGraphicsScene):
            widget: Optional[HeatmapGridWidget] = None

        self.scene = self.scene = HeatmapScene(parent=self)
        self.view = GraphicsView(
            self.scene,
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            viewportUpdateMode=QGraphicsView.FullViewportUpdate,
            widgetResizable=True,
        )
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(
            self._on_view_context_menu
        )
        self.mainArea.layout().addWidget(self.view)
        self.selected_rows = []
        self.__font_inc = QAction(
            "Increase Font", self, shortcut=QKeySequence("ctrl+>"))
        self.__font_dec = QAction(
            "Decrease Font", self, shortcut=QKeySequence("ctrl+<"))
        self.__font_inc.triggered.connect(lambda: self.__adjust_font_size(1))
        self.__font_dec.triggered.connect(lambda: self.__adjust_font_size(-1))
        if hasattr(QAction, "setShortcutVisibleInContextMenu"):
            apply_all(
                [self.__font_inc, self.__font_dec],
                lambda a: a.setShortcutVisibleInContextMenu(True)
            )
        self.addActions([self.__font_inc, self.__font_dec])

    @property
    def center_palette(self):
        palette = self.color_map_widget.currentData()
        return bool(palette.flags & palette.Diverging)

    @property
    def _column_label_pos(self) -> HeatmapGridWidget.Position:
        return ColumnLabelsPosData[self.column_label_pos][Qt.UserRole]

    def annotation_color_var_changed(self, value):
        cbselect(self.row_side_color_cb, value, Qt.EditRole)

    def annotation_var_changed(self, value):
        cbselect(self.annotation_text_cb, value, Qt.EditRole)

    def set_row_clustering(self, method: Clustering) -> None:
        assert isinstance(method, Clustering)
        if self.row_clustering != method:
            self.row_clustering = method
            cbselect(self.row_cluster_cb, method, ClusteringRole)
            self.__update_row_clustering()

    def set_col_clustering(self, method: Clustering) -> None:
        assert isinstance(method, Clustering)
        if self.col_clustering != method:
            self.col_clustering = method
            cbselect(self.col_cluster_cb, method, ClusteringRole)
            self.__update_column_clustering()

    def sizeHint(self) -> QSize:
        return super().sizeHint().expandedTo(QSize(900, 700))

    def color_palette(self):
        return self.color_map_widget.currentData().lookup_table()

    def color_map(self) -> GradientColorMap:
        return GradientColorMap(
            self.color_palette(), (self.threshold_low, self.threshold_high),
            0 if self.center_palette else None
        )

    def clear(self):
        self.data = None
        self.input_data = None
        self.effective_data = None
        self.kmeans_model = None
        self.merge_indices = None
        self.annotation_model.set_domain(None)
        self.annotation_var = None
        self.row_side_color_model.set_domain(None)
        self.annotation_color_var = None
        self.row_split_model.set_domain(None)
        self.split_by_var = None
        self.parts = None
        self.clear_scene()
        self.selected_rows = []
        self.__columns_cache.clear()
        self.__rows_cache.clear()
        self.__update_clustering_enable_state(None)

    def clear_scene(self):
        if self.scene.widget is not None:
            self.scene.widget.layoutDidActivate.disconnect(
                self.__on_layout_activate
            )
            self.scene.widget.selectionFinished.disconnect(
                self.on_selection_finished
            )
        self.scene.widget = None
        self.scene.clear()

        self.view.setSceneRect(QRectF())
        self.view.setHeaderSceneRect(QRectF())
        self.view.setFooterSceneRect(QRectF())

    @Inputs.data
    def set_dataset(self, data=None):
        """Set the input dataset to display."""
        self.closeContext()
        self.clear()
        self.clear_messages()
        self._set_input_summary(data)

        if isinstance(data, SqlTable):
            if data.approx_len() < 4000:
                data = Table(data)
            else:
                self.Information.sampled()
                data_sample = data.sample_time(1, no_cache=True)
                data_sample.download_data(2000, partial=True)
                data = Table(data_sample)

        if data is not None and not len(data):
            data = None

        if data is not None and sp.issparse(data.X):
            try:
                data = data.to_dense()
            except MemoryError:
                data = None
                self.Error.not_enough_memory()
            else:
                self.Information.sparse_densified()

        input_data = data

        # Data contains no attributes or meta attributes only
        if data is not None and len(data.domain.attributes) == 0:
            self.Error.no_continuous()
            input_data = data = None

        # Data contains some discrete attributes which must be filtered
        if data is not None and \
                any(var.is_discrete for var in data.domain.attributes):
            ndisc = sum(var.is_discrete for var in data.domain.attributes)
            data = data.transform(
                Domain([var for var in data.domain.attributes
                        if var.is_continuous],
                       data.domain.class_vars,
                       data.domain.metas))
            if not data.domain.attributes:
                self.Error.no_continuous()
                input_data = data = None
            else:
                self.Information.discrete_ignored(
                    ndisc, "s" if ndisc > 1 else "")

        self.data = data
        self.input_data = input_data

        if data is not None:
            self.annotation_model.set_domain(self.input_data.domain)
            self.row_side_color_model.set_domain(self.input_data.domain)
            self.annotation_var = None
            self.annotation_color_var = None
            self.row_split_model.set_domain(data.domain)
            if data.domain.has_discrete_class:
                self.split_by_var = data.domain.class_var
            else:
                self.split_by_var = None
            self.openContext(self.input_data)
            if self.split_by_var not in self.row_split_model:
                self.split_by_var = None

        self.update_heatmaps()
        if data is not None and self.__pending_selection is not None:
            assert self.scene.widget is not None
            self.scene.widget.selectRows(self.__pending_selection)
            self.selected_rows = self.__pending_selection
            self.__pending_selection = None

        self.unconditional_commit()

    def _set_input_summary(self, data):
        summary = len(data) if data else self.info.NoInput
        details = format_summary_details(data) if data else ""
        self.info.set_input_summary(summary, details)

    def __on_split_rows_activated(self):
        self.set_split_variable(self.row_split_cb.currentData(Qt.EditRole))

    def set_split_variable(self, var):
        if var is not self.split_by_var:
            self.split_by_var = var
            self.update_heatmaps()

    def update_heatmaps(self):
        if self.data is not None:
            self.clear_scene()
            self.clear_messages()
            if self.col_clustering != Clustering.None_ and \
                    len(self.data.domain.attributes) < 2:
                self.Error.not_enough_features()
            elif (self.col_clustering != Clustering.None_ or
                  self.row_clustering != Clustering.None_) and \
                    len(self.data) < 2:
                self.Error.not_enough_instances()
            elif self.merge_kmeans and len(self.data) < 3:
                self.Error.not_enough_instances_k_means()
            else:
                parts = self.construct_heatmaps(self.data, self.split_by_var)
                self.construct_heatmaps_scene(parts, self.effective_data)
                self.selected_rows = []
        else:
            self.clear()

    def update_merge(self):
        self.kmeans_model = None
        self.merge_indices = None
        if self.data is not None and self.merge_kmeans:
            self.update_heatmaps()
            self.commit()

    def _make_parts(self, data, group_var=None):
        """
        Make initial `Parts` for data, split by group_var, group_key
        """
        if group_var is not None:
            assert group_var.is_discrete
            _col_data = table_column_data(data, group_var)
            row_indices = [np.flatnonzero(_col_data == i)
                           for i in range(len(group_var.values))]

            row_groups = [RowPart(title=name, indices=ind,
                                  cluster=None, cluster_ordered=None)
                          for name, ind in zip(group_var.values, row_indices)]
            if np.any(_col_data.mask):
                row_groups.append(RowPart(
                    title="N/A", indices=np.flatnonzero(_col_data.mask),
                    cluster=None, cluster_ordered=None
                ))
        else:
            row_groups = [RowPart(title=None, indices=range(0, len(data)),
                                  cluster=None, cluster_ordered=None)]

        col_groups = [
            ColumnPart(
                title=None, indices=range(0, len(data.domain.attributes)),
                domain=data.domain, cluster=None, cluster_ordered=None)
        ]

        minv, maxv = np.nanmin(data.X), np.nanmax(data.X)
        return Parts(row_groups, col_groups, span=(minv, maxv))

    def cluster_rows(self, data: Table, parts: 'Parts', ordered=False) -> 'Parts':
        row_groups = []
        for row in parts.rows:
            if row.cluster is not None:
                cluster = row.cluster
            else:
                cluster = None
            if row.cluster_ordered is not None:
                cluster_ord = row.cluster_ordered
            else:
                cluster_ord = None

            if row.can_cluster:
                matrix = None
                need_dist = cluster is None or (ordered and cluster_ord is None)
                if need_dist:
                    subset = data[row.indices]
                    matrix = Orange.distance.Euclidean(subset)

                if cluster is None:
                    assert len(matrix) < self.MaxClustering
                    cluster = hierarchical.dist_matrix_clustering(
                        matrix, linkage=hierarchical.WARD
                    )
                if ordered and cluster_ord is None:
                    assert len(matrix) < self.MaxOrderedClustering
                    cluster_ord = hierarchical.optimal_leaf_ordering(
                        cluster, matrix,
                    )
            row_groups.append(row._replace(cluster=cluster, cluster_ordered=cluster_ord))

        return parts._replace(rows=row_groups)

    def cluster_columns(self, data, parts, ordered=False):
        assert len(parts.columns) == 1, "columns split is no longer supported"
        assert all(var.is_continuous for var in data.domain.attributes)

        col0 = parts.columns[0]
        if col0.cluster is not None:
            cluster = col0.cluster
        else:
            cluster = None
        if col0.cluster_ordered is not None:
            cluster_ord = col0.cluster_ordered
        else:
            cluster_ord = None
        need_dist = cluster is None or (ordered and cluster_ord is None)
        matrix = None
        if need_dist:
            data = Orange.distance._preprocess(data)
            matrix = np.asarray(Orange.distance.PearsonR(data, axis=0))
            # nan values break clustering below
            matrix = np.nan_to_num(matrix)

        if cluster is None:
            assert matrix is not None
            assert len(matrix) < self.MaxClustering
            cluster = hierarchical.dist_matrix_clustering(
                matrix, linkage=hierarchical.WARD
            )
        if ordered and cluster_ord is None:
            assert len(matrix) < self.MaxOrderedClustering
            cluster_ord = hierarchical.optimal_leaf_ordering(cluster, matrix)

        col_groups = [col._replace(cluster=cluster, cluster_ordered=cluster_ord)
                      for col in parts.columns]
        return parts._replace(columns=col_groups)

    def construct_heatmaps(self, data, group_var=None) -> 'Parts':
        if self.merge_kmeans:
            if self.kmeans_model is None:
                effective_data = self.input_data.transform(
                    Orange.data.Domain(
                        [var for var in self.input_data.domain.attributes
                         if var.is_continuous],
                        self.input_data.domain.class_vars,
                        self.input_data.domain.metas))
                nclust = min(self.merge_kmeans_k, len(effective_data) - 1)
                self.kmeans_model = kmeans_compress(effective_data, k=nclust)
                effective_data.domain = self.kmeans_model.domain
                merge_indices = [np.flatnonzero(self.kmeans_model.labels == ind)
                                 for ind in range(nclust)]
                not_empty_indices = [i for i, x in enumerate(merge_indices)
                                     if len(x) > 0]
                self.merge_indices = \
                    [merge_indices[i] for i in not_empty_indices]
                if len(merge_indices) != len(self.merge_indices):
                    self.Warning.empty_clusters()
                effective_data = Orange.data.Table(
                    Orange.data.Domain(effective_data.domain.attributes),
                    self.kmeans_model.centroids[not_empty_indices]
                )
            else:
                effective_data = self.effective_data

            group_var = None
        else:
            self.kmeans_model = None
            self.merge_indices = None
            effective_data = data

        self.effective_data = effective_data

        self.__update_clustering_enable_state(effective_data)

        parts = self._make_parts(effective_data, group_var)
        # Restore/update the row/columns items descriptions from cache if
        # available
        rows_cache_key = (group_var,
                          self.merge_kmeans_k if self.merge_kmeans else None)
        if rows_cache_key in self.__rows_cache:
            parts = parts._replace(rows=self.__rows_cache[rows_cache_key].rows)

        if self.row_clustering != Clustering.None_:
            parts = self.cluster_rows(
                effective_data, parts,
                ordered=self.row_clustering == Clustering.OrderedClustering
            )
        if self.col_clustering != Clustering.None_:
            parts = self.cluster_columns(
                effective_data, parts,
                ordered=self.col_clustering == Clustering.OrderedClustering
            )

        # Cache the updated parts
        self.__rows_cache[rows_cache_key] = parts
        return parts

    def construct_heatmaps_scene(self, parts: 'Parts', data: Table) -> None:
        _T = TypeVar("_T", bound=Union[RowPart, ColumnPart])

        def select_cluster(clustering: Clustering, item: _T) -> _T:
            if clustering == Clustering.None_:
                return item._replace(cluster=None, cluster_ordered=None)
            elif clustering == Clustering.Clustering:
                return item._replace(cluster=item.cluster, cluster_ordered=None)
            elif clustering == Clustering.OrderedClustering:
                return item._replace(cluster=item.cluster_ordered, cluster_ordered=None)
            else:  # pragma: no cover
                raise TypeError()

        rows = [select_cluster(self.row_clustering, rowitem)
                for rowitem in parts.rows]
        cols = [select_cluster(self.col_clustering, colitem)
                for colitem in parts.columns]
        parts = Parts(columns=cols, rows=rows, span=parts.span)

        self.setup_scene(parts, data)

    def setup_scene(self, parts, data):
        # type: (Parts, Table) -> None
        widget = HeatmapGridWidget()
        widget.setColorMap(self.color_map())
        self.scene.addItem(widget)
        self.scene.widget = widget
        columns = [v.name for v in data.domain.attributes]
        parts = HeatmapGridWidget.Parts(
            rows=[
                HeatmapGridWidget.RowItem(r.title, r.indices, r.cluster)
                for r in parts.rows
            ],
            columns=[
                HeatmapGridWidget.ColumnItem(c.title, c.indices, c.cluster)
                for c in parts.columns
            ],
            data=data.X,
            span=parts.span,
            row_names=None,
            col_names=columns,
        )
        widget.setHeatmaps(parts)
        side = self.row_side_colors()
        if side is not None:
            widget.setRowSideColorAnnotations(side[0], side[1], name=side[2].name)
        widget.setColumnLabelsPosition(self._column_label_pos)
        widget.setAspectRatioMode(
            Qt.KeepAspectRatio if self.keep_aspect else Qt.IgnoreAspectRatio
        )
        widget.setShowAverages(self.averages)
        widget.setLegendVisible(self.legend)

        widget.layoutDidActivate.connect(self.__on_layout_activate)
        widget.selectionFinished.connect(self.on_selection_finished)

        self.update_annotations()
        self.view.setCentralWidget(widget)
        self.parts = parts

    def __update_scene_rects(self):
        widget = self.scene.widget
        if widget is None:
            return
        rect = widget.geometry()
        self.scene.setSceneRect(rect)
        self.view.setSceneRect(rect)
        self.view.setHeaderSceneRect(widget.headerGeometry())
        self.view.setFooterSceneRect(widget.footerGeometry())

    def __on_layout_activate(self):
        self.__update_scene_rects()

    def __aspect_mode_changed(self):
        widget = self.scene.widget
        if widget is None:
            return
        widget.setAspectRatioMode(
            Qt.KeepAspectRatio if self.keep_aspect else Qt.IgnoreAspectRatio
        )
        # when aspect fixed the vertical sh is fixex, when not, it can
        # shrink vertically
        sp = widget.sizePolicy()
        if self.keep_aspect:
            sp.setVerticalPolicy(QSizePolicy.Fixed)
        else:
            sp.setVerticalPolicy(QSizePolicy.Preferred)
        widget.setSizePolicy(sp)

    def __update_clustering_enable_state(self, data):
        if data is not None:
            N = len(data)
            M = len(data.domain.attributes)
        else:
            N = M = 0

        rc_enabled = N <= self.MaxClustering
        rco_enabled = N <= self.MaxOrderedClustering
        cc_enabled = M <= self.MaxClustering
        cco_enabled = M <= self.MaxOrderedClustering
        row_clust, col_clust = self.row_clustering, self.col_clustering

        row_clust_msg = ""
        col_clust_msg = ""

        if not rco_enabled and row_clust == Clustering.OrderedClustering:
            row_clust = Clustering.Clustering
            row_clust_msg = "Row cluster ordering was disabled due to the " \
                            "input matrix being to big"
        if not rc_enabled and row_clust == Clustering.Clustering:
            row_clust = Clustering.None_
            row_clust_msg = "Row clustering was was disabled due to the " \
                            "input matrix being to big"

        if not cco_enabled and col_clust == Clustering.OrderedClustering:
            col_clust = Clustering.Clustering
            col_clust_msg = "Column cluster ordering was disabled due to " \
                            "the input matrix being to big"
        if not cc_enabled and col_clust == Clustering.Clustering:
            col_clust = Clustering.None_
            col_clust_msg = "Column clustering was disabled due to the " \
                            "input matrix being to big"

        self.col_clustering = col_clust
        self.row_clustering = row_clust

        self.Information.row_clust(row_clust_msg, shown=bool(row_clust_msg))
        self.Information.col_clust(col_clust_msg, shown=bool(col_clust_msg))

        # Disable/enable the combobox items for the clustering methods
        def setenabled(cb: QComboBox, clu: bool, clu_op: bool):
            model = cb.model()
            assert isinstance(model, QStandardItemModel)
            idx = cb.findData(Clustering.OrderedClustering, ClusteringRole)
            assert idx != -1
            model.item(idx).setEnabled(clu_op)
            idx = cb.findData(Clustering.Clustering, ClusteringRole)
            assert idx != -1
            model.item(idx).setEnabled(clu)

        setenabled(self.row_cluster_cb, rc_enabled, rco_enabled)
        setenabled(self.col_cluster_cb, cc_enabled, cco_enabled)

    def update_averages_stripe(self):
        """Update the visibility of the averages stripe.
        """
        widget = self.scene.widget
        if widget is not None:
            widget.setShowAverages(self.averages)

    def update_color_schema(self):
        self.palette_name = self.color_map_widget.currentData().name
        w = self.scene.widget
        if w is not None:
            w.setColorMap(self.color_map())

    def __update_column_clustering(self):
        self.update_heatmaps()
        self.commit()

    def __update_row_clustering(self):
        self.update_heatmaps()
        self.commit()

    def update_legend(self):
        widget = self.scene.widget
        if widget is not None:
            widget.setLegendVisible(self.legend)

    def row_annotation_var(self):
        return self.annotation_var

    def row_annotation_data(self):
        var = self.row_annotation_var()
        if var is None:
            return None
        return column_str_from_table(self.input_data, var)

    def _merge_row_indices(self):
        if self.merge_kmeans and self.kmeans_model is not None:
            return self.merge_indices
        else:
            return None

    def set_annotation_var(self, var: Union[None, Variable, int]):
        if isinstance(var, int):
            var = self.annotation_model[var]
        if self.annotation_var is not var:
            self.annotation_var = var
            self.update_annotations()

    def update_annotations(self):
        widget = self.scene.widget
        if widget is not None:
            annot_col = self.row_annotation_data()
            merge_indices = self._merge_row_indices()
            if merge_indices is not None and annot_col is not None:
                join = lambda _1: join_elided(", ", 42, _1, " ({} more)")
                annot_col = aggregate_apply(join, annot_col, merge_indices)
            if annot_col is not None:
                widget.setRowLabels(annot_col)
                widget.setRowLabelsVisible(True)
            else:
                widget.setRowLabelsVisible(False)
                widget.setRowLabels(None)

    def row_side_colors(self):
        var = self.annotation_color_var
        if var is None:
            return None
        column_data = column_data_from_table(self.input_data, var)
        merges = self._merge_row_indices()
        if merges is not None:
            column_data = aggregate(var, column_data, merges)
        data, colormap = self._colorize(var, column_data)
        if var.is_continuous:
            span = (np.nanmin(column_data), np.nanmax(column_data))
            if np.any(np.isnan(span)):
                span = 0., 1.
            colormap.span = span
        return data, colormap, var

    def set_annotation_color_var(self, var: Union[None, Variable, int]):
        """Set the current side color annotation variable."""
        if isinstance(var, int):
            var = self.row_side_color_model[var]
        if self.annotation_color_var is not var:
            self.annotation_color_var = var
            self.update_row_side_colors()

    def update_row_side_colors(self):
        widget = self.scene.widget
        if widget is None:
            return
        colors = self.row_side_colors()
        if colors is None:
            widget.setRowSideColorAnnotations(None)
        else:
            widget.setRowSideColorAnnotations(colors[0], colors[1], colors[2].name)

    def _colorize(self, var: Variable, data: np.ndarray) -> Tuple[np.ndarray, ColorMap]:
        palette = var.palette  # type: Palette
        colors = np.array(
            [[c.red(), c.green(), c.blue()] for c in palette.qcolors_w_nan],
            dtype=np.uint8,
        )
        if var.is_discrete:
            mask = np.isnan(data)
            data[mask] = -1
            data = data.astype(int)
            if mask.any():
                values = (*var.values, "N/A")
            else:
                values = var.values
                colors = colors[: -1]
            return data, CategoricalColorMap(colors, values)
        elif var.is_continuous:
            cmap = GradientColorMap(colors[:-1])
            return data, cmap
        else:
            raise TypeError

    def update_column_annotations(self):
        widget = self.scene.widget
        if self.data is not None and widget is not None:
            widget.setColumnLabelsPosition(self._column_label_pos)

    def __adjust_font_size(self, diff):
        widget = self.scene.widget
        if widget is None:
            return
        curr = widget.font().pointSizeF()
        new = curr + diff

        self.__font_dec.setEnabled(new > 1.0)
        self.__font_inc.setEnabled(new <= 32)
        if new > 1.0:
            font = QFont()
            font.setPointSizeF(new)
            widget.setFont(font)

    def _on_view_context_menu(self, pos):
        widget = self.scene.widget
        if widget is None:
            return
        assert isinstance(widget, HeatmapGridWidget)
        menu = QMenu(self.view.viewport())
        menu.setAttribute(Qt.WA_DeleteOnClose)
        menu.addActions(self.view.actions())
        menu.addSeparator()
        menu.addActions([self.__font_inc, self.__font_dec])
        menu.addSeparator()
        a = QAction("Keep aspect ratio", menu, checkable=True)
        a.setChecked(self.keep_aspect)

        def ontoggled(state):
            self.keep_aspect = state
            self.__aspect_mode_changed()
        a.toggled.connect(ontoggled)
        menu.addAction(a)
        menu.popup(self.view.viewport().mapToGlobal(pos))

    def on_selection_finished(self):
        if self.scene.widget is not None:
            self.selected_rows = list(self.scene.widget.selectedRows())
        else:
            self.selected_rows = []
        self.commit()

    def commit(self):
        data = None
        indices = None
        if self.merge_kmeans:
            merge_indices = self.merge_indices
        else:
            merge_indices = None

        if self.input_data is not None and self.selected_rows:
            indices = self.selected_rows
            if merge_indices is not None:
                # expand merged indices
                indices = np.hstack([merge_indices[i] for i in indices])

            data = self.input_data[indices]

        summary = len(data) if data else self.info.NoOutput
        details = format_summary_details(data) if data else ""
        self.info.set_output_summary(summary, details)
        self.Outputs.selected_data.send(data)
        self.Outputs.annotated_data.send(create_annotated_table(self.input_data, indices))

    def onDeleteWidget(self):
        self.clear()
        super().onDeleteWidget()

    def send_report(self):
        self.report_items((
            ("Columns:", "Clustering" if self.col_clustering else "No sorting"),
            ("Rows:", "Clustering" if self.row_clustering else "No sorting"),
            ("Split:",
             self.split_by_var is not None and self.split_by_var.name),
            ("Row annotation",
             self.annotation_var is not None and self.annotation_var.name),
        ))
        self.report_plot()

    @classmethod
    def migrate_settings(cls, settings, version):
        if version is not None and version < 3:
            def st2cl(state: bool) -> Clustering:
                return Clustering.OrderedClustering if state else \
                    Clustering.None_

            rc = settings.pop("row_clustering", False)
            cc = settings.pop("col_clustering", False)
            settings["row_clustering_method"] = st2cl(rc).name
            settings["col_clustering_method"] = st2cl(cc).name
Пример #51
0
class OWPaintData(OWWidget):
    TOOLS = [
        ("Brush", "Create multiple instances", AirBrushTool, _i("brush.svg")),
        ("Put", "Put individual instances", PutInstanceTool, _i("put.svg")),
        ("Select", "Select and move instances", SelectTool,
            _i("select-transparent_42px.png")),
        ("Jitter", "Jitter instances", JitterTool, _i("jitter.svg")),
        ("Magnet", "Attract multiple instances", MagnetTool, _i("magnet.svg")),
        ("Clear", "Clear the plot", ClearTool, _i("../../../icons/Dlg_clear.png"))
    ]

    name = "Paint Data"
    description = "Create data by painting data points on a plane."
    icon = "icons/PaintData.svg"
    priority = 15
    keywords = ["data", "paint", "create"]

    outputs = [("Data", Orange.data.Table)]
    inputs = [("Data", Orange.data.Table, "set_data")]

    autocommit = Setting(False)
    table_name = Setting("Painted data")
    attr1 = Setting("x")
    attr2 = Setting("y")
    hasAttr2 = Setting(True)

    brushRadius = Setting(75)
    density = Setting(7)

    data = Setting(None, schema_only=True)

    graph_name = "plot"

    class Warning(OWWidget.Warning):
        no_input_variables = Msg("Input data has no variables")
        continuous_target = Msg("Continuous target value can not be used.")

    class Information(OWWidget.Information):
        use_first_two = \
            Msg("Paint Data uses data from the first two attributes.")

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

        self.input_data = None
        self.input_classes = []
        self.input_has_attr2 = True
        self.current_tool = None
        self._selected_indices = None
        self._scatter_item = None

        self.labels = ["C1", "C2"]

        self.undo_stack = QUndoStack(self)

        self.class_model = ColoredListModel(
            self.labels, self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled |
                  Qt.ItemIsEditable)

        self.class_model.dataChanged.connect(self._class_value_changed)
        self.class_model.rowsInserted.connect(self._class_count_changed)
        self.class_model.rowsRemoved.connect(self._class_count_changed)

        if self.data is None:
            self.data = np.zeros((0, 3))
        self.colors = colorpalette.ColorPaletteGenerator(
            len(colorpalette.DefaultRGBColors))
        self.tools_cache = {}

        self._init_ui()
        self.commit()

    def _init_ui(self):
        namesBox = gui.vBox(self.controlArea, "Names")

        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        gui.lineEdit(hbox, self, "attr1", "Variable X: ",
                     controlWidth=80, orientation=Qt.Horizontal,
                     callback=self._attr_name_changed)
        gui.separator(hbox, 21)
        hbox = gui.hBox(namesBox, margin=0, spacing=0)
        attr2 = gui.lineEdit(hbox, self, "attr2", "Variable Y: ",
                             controlWidth=80, orientation=Qt.Horizontal,
                             callback=self._attr_name_changed)
        gui.separator(hbox)
        gui.checkBox(hbox, self, "hasAttr2", '', disables=attr2,
                     labelWidth=0,
                     callback=self.set_dimensions)
        gui.separator(namesBox)

        gui.widgetLabel(namesBox, "Labels")
        self.classValuesView = listView = QListView(
            selectionMode=QListView.SingleSelection,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored,
                                   QSizePolicy.Maximum)
        )
        listView.setModel(self.class_model)
        itemmodels.select_row(listView, 0)
        namesBox.layout().addWidget(listView)

        self.addClassLabel = QAction(
            "+", self,
            toolTip="Add new class label",
            triggered=self.add_new_class_label
        )

        self.removeClassLabel = QAction(
            unicodedata.lookup("MINUS SIGN"), self,
            toolTip="Remove selected class label",
            triggered=self.remove_selected_class_label
        )

        actionsWidget = itemmodels.ModelActionsWidget(
            [self.addClassLabel, self.removeClassLabel], self
        )
        actionsWidget.layout().addStretch(10)
        actionsWidget.layout().setSpacing(1)
        namesBox.layout().addWidget(actionsWidget)

        tBox = gui.vBox(self.controlArea, "Tools", addSpace=True)
        buttonBox = gui.hBox(tBox)
        toolsBox = gui.widgetBox(buttonBox, orientation=QGridLayout())

        self.toolActions = QActionGroup(self)
        self.toolActions.setExclusive(True)
        self.toolButtons = []

        for i, (name, tooltip, tool, icon) in enumerate(self.TOOLS):
            action = QAction(
                name, self,
                toolTip=tooltip,
                checkable=tool.checkable,
                icon=QIcon(icon),
            )
            action.triggered.connect(partial(self.set_current_tool, tool))

            button = QToolButton(
                iconSize=QSize(24, 24),
                toolButtonStyle=Qt.ToolButtonTextUnderIcon,
                sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.Fixed)
            )
            button.setDefaultAction(action)
            self.toolButtons.append((button, tool))

            toolsBox.layout().addWidget(button, i / 3, i % 3)
            self.toolActions.addAction(action)

        for column in range(3):
            toolsBox.layout().setColumnMinimumWidth(column, 10)
            toolsBox.layout().setColumnStretch(column, 1)

        undo = self.undo_stack.createUndoAction(self)
        redo = self.undo_stack.createRedoAction(self)

        undo.setShortcut(QKeySequence.Undo)
        redo.setShortcut(QKeySequence.Redo)

        self.addActions([undo, redo])
        self.undo_stack.indexChanged.connect(lambda _: self.invalidate())

        gui.separator(tBox)
        indBox = gui.indentedBox(tBox, sep=8)
        form = QFormLayout(
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )
        indBox.layout().addLayout(form)
        slider = gui.hSlider(
            indBox, self, "brushRadius", minValue=1, maxValue=100,
            createLabel=False
        )
        form.addRow("Radius:", slider)

        slider = gui.hSlider(
            indBox, self, "density", None, minValue=1, maxValue=100,
            createLabel=False
        )

        form.addRow("Intensity:", slider)
        self.btResetToInput = gui.button(
            tBox, self, "Reset to Input Data", self.reset_to_input)
        self.btResetToInput.setDisabled(True)

        gui.rubber(self.controlArea)
        gui.auto_commit(self.left_side, self, "autocommit",
                        "Send")

        # main area GUI
        viewbox = PaintViewBox(enableMouse=False)
        self.plotview = pg.PlotWidget(background="w", viewBox=viewbox)
        self.plotview.sizeHint = lambda: QSize(200, 100)  # Minimum size for 1-d painting
        self.plot = self.plotview.getPlotItem()

        axis_color = self.palette().color(QPalette.Text)
        axis_pen = QPen(axis_color)

        tickfont = QFont(self.font())
        tickfont.setPixelSize(max(int(tickfont.pixelSize() * 2 // 3), 11))

        axis = self.plot.getAxis("bottom")
        axis.setLabel(self.attr1)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)

        axis = self.plot.getAxis("left")
        axis.setLabel(self.attr2)
        axis.setPen(axis_pen)
        axis.setTickFont(tickfont)
        if not self.hasAttr2:
            self.plot.hideAxis('left')

        self.plot.hideButtons()
        self.plot.setXRange(0, 1, padding=0.01)

        self.mainArea.layout().addWidget(self.plotview)

        # enable brush tool
        self.toolActions.actions()[0].setChecked(True)
        self.set_current_tool(self.TOOLS[0][2])

        self.set_dimensions()

    def set_dimensions(self):
        if self.hasAttr2:
            self.plot.setYRange(0, 1, padding=0.01)
            self.plot.showAxis('left')
            self.plotview.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Minimum)
        else:
            self.plot.setYRange(-.5, .5, padding=0.01)
            self.plot.hideAxis('left')
            self.plotview.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Maximum)
        self._replot()
        for button, tool in self.toolButtons:
            if tool.only2d:
                button.setDisabled(not self.hasAttr2)

    def set_data(self, data):
        """Set the input_data and call reset_to_input"""
        def _check_and_set_data(data):
            self.clear_messages()
            if data is not None:
                if not data.domain.attributes:
                    self.Warning.no_input_variables()
                    data = None
                elif len(data.domain.attributes) > 2:
                    self.Information.use_first_two()
            self.input_data = data
            self.btResetToInput.setDisabled(data is None)
            return data is not None

        if not _check_and_set_data(data):
            return

        X = np.array([scale(vals) for vals in data.X[:, :2].T]).T
        try:
            y = next(cls for cls in data.domain.class_vars if cls.is_discrete)
        except StopIteration:
            if data.domain.class_vars:
                self.Warning.continuous_target()
            self.input_classes = ["C1"]
            y = np.zeros(len(data))
        else:
            self.input_classes = y.values
            y = data[:, y].Y

        self.input_has_attr2 = len(data.domain.attributes) >= 2
        if not self.input_has_attr2:
            self.input_data = np.column_stack((X, np.zeros(len(data)), y))
        else:
            self.input_data = np.column_stack((X, y))
        self.reset_to_input()

    def reset_to_input(self):
        """Reset the painting to input data if present."""
        if self.input_data is None:
            return
        self.undo_stack.clear()

        index = self.selected_class_label()
        self.class_model[:] = self.input_classes
        newindex = min(max(index, 0), len(self.class_model) - 1)
        itemmodels.select_row(self.classValuesView, newindex)

        self.data = self.input_data
        prev_attr2 = self.hasAttr2
        self.hasAttr2 = self.input_has_attr2
        if prev_attr2 != self.hasAttr2:
            self.set_dimensions()
        else:  # set_dimensions already calls _replot, no need to call it again
            self._replot()

    def add_new_class_label(self, undoable=True):

        newlabel = next(label for label in namegen('C', 1)
                        if label not in self.class_model)

        command = SimpleUndoCommand(
            lambda: self.class_model.append(newlabel),
            lambda: self.class_model.__delitem__(-1)
        )
        if undoable:
            self.undo_stack.push(command)
        else:
            command.redo()

    def remove_selected_class_label(self):
        index = self.selected_class_label()

        if index is None:
            return

        label = self.class_model[index]
        mask = self.data[:, 2] == index
        move_mask = self.data[~mask][:, 2] > index

        self.undo_stack.beginMacro("Delete class label")
        self.undo_stack.push(UndoCommand(DeleteIndices(mask), self))
        self.undo_stack.push(UndoCommand(Move((move_mask, 2), -1), self))
        self.undo_stack.push(
            SimpleUndoCommand(lambda: self.class_model.__delitem__(index),
                              lambda: self.class_model.insert(index, label)))
        self.undo_stack.endMacro()

        newindex = min(max(index - 1, 0), len(self.class_model) - 1)
        itemmodels.select_row(self.classValuesView, newindex)

    def _class_count_changed(self):
        self.labels = list(self.class_model)
        self.removeClassLabel.setEnabled(len(self.class_model) > 1)
        self.addClassLabel.setEnabled(
            len(self.class_model) < self.colors.number_of_colors)
        if self.selected_class_label() is None:
            itemmodels.select_row(self.classValuesView, 0)

    def _class_value_changed(self, index, _):
        index = index.row()
        newvalue = self.class_model[index]
        oldvalue = self.labels[index]
        if newvalue != oldvalue:
            self.labels[index] = newvalue
#             command = Command(
#                 lambda: self.class_model.__setitem__(index, newvalue),
#                 lambda: self.class_model.__setitem__(index, oldvalue),
#             )
#             self.undo_stack.push(command)

    def selected_class_label(self):
        rows = self.classValuesView.selectedIndexes()
        if rows:
            return rows[0].row()
        else:
            return None

    def set_current_tool(self, tool):
        prev_tool = self.current_tool.__class__

        if self.current_tool is not None:
            self.current_tool.deactivate()
            self.current_tool.editingStarted.disconnect(
                self._on_editing_started)
            self.current_tool.editingFinished.disconnect(
                self._on_editing_finished)
            self.current_tool = None
            self.plot.getViewBox().tool = None

        if tool not in self.tools_cache:
            newtool = tool(self, self.plot)
            self.tools_cache[tool] = newtool
            newtool.issueCommand.connect(self._add_command)

        self._selected_region = QRectF()
        self.current_tool = tool = self.tools_cache[tool]
        self.plot.getViewBox().tool = tool
        tool.editingStarted.connect(self._on_editing_started)
        tool.editingFinished.connect(self._on_editing_finished)
        tool.activate()

        if not tool.checkable:
            self.set_current_tool(prev_tool)

    def _on_editing_started(self):
        self.undo_stack.beginMacro("macro")

    def _on_editing_finished(self):
        self.undo_stack.endMacro()

    def execute(self, command):
        if isinstance(command, (Append, DeleteIndices, Insert, Move)):
            if isinstance(command, (DeleteIndices, Insert)):
                self._selected_indices = None

                if isinstance(self.current_tool, SelectTool):
                    self.current_tool._reset()

            self.data, undo = transform(command, self.data)
            self._replot()
            return undo
        else:
            assert False, "Non normalized command"

    def _add_command(self, cmd):
        name = "Name"

        if (not self.hasAttr2 and
            isinstance(cmd, (Move, MoveSelection, Jitter, Magnet))):
            # tool only supported if both x and y are enabled
            return

        if isinstance(cmd, Append):
            cls = self.selected_class_label()
            points = np.array([(p.x(), p.y() if self.hasAttr2 else 0, cls)
                                  for p in cmd.points])
            self.undo_stack.push(UndoCommand(Append(points), self, text=name))
        elif isinstance(cmd, Move):
            self.undo_stack.push(UndoCommand(cmd, self, text=name))
        elif isinstance(cmd, SelectRegion):
            indices = [i for i, (x, y) in enumerate(self.data[:, :2])
                       if cmd.region.contains(QPointF(x, y))]
            indices = np.array(indices, dtype=int)
            self._selected_indices = indices
        elif isinstance(cmd, DeleteSelection):
            indices = self._selected_indices
            if indices is not None and indices.size:
                self.undo_stack.push(
                    UndoCommand(DeleteIndices(indices), self, text="Delete")
                )
        elif isinstance(cmd, MoveSelection):
            indices = self._selected_indices
            if indices is not None and indices.size:
                self.undo_stack.push(
                    UndoCommand(
                        Move((self._selected_indices, slice(0, 2)),
                             np.array([cmd.delta.x(), cmd.delta.y()])),
                        self, text="Move")
                )
        elif isinstance(cmd, DeleteIndices):
            self.undo_stack.push(UndoCommand(cmd, self, text="Delete"))
        elif isinstance(cmd, Insert):
            self.undo_stack.push(UndoCommand(cmd, self))
        elif isinstance(cmd, AirBrush):
            data = create_data(cmd.pos.x(), cmd.pos.y(),
                               self.brushRadius / 1000,
                               int(1 + self.density / 20), cmd.rstate)
            self._add_command(Append([QPointF(*p) for p in zip(*data.T)]))
        elif isinstance(cmd, Jitter):
            point = np.array([cmd.pos.x(), cmd.pos.y()])
            delta = - apply_jitter(self.data[:, :2], point,
                                   self.density / 100.0, 0, cmd.rstate)
            self._add_command(Move((..., slice(0, 2)), delta))
        elif isinstance(cmd, Magnet):
            point = np.array([cmd.pos.x(), cmd.pos.y()])
            delta = - apply_attractor(self.data[:, :2], point,
                                      self.density / 100.0, 0)
            self._add_command(Move((..., slice(0, 2)), delta))
        else:
            assert False, "unreachable"

    def _replot(self):
        def pen(color):
            pen = QPen(color, 1)
            pen.setCosmetic(True)
            return pen

        if self._scatter_item is not None:
            self.plot.removeItem(self._scatter_item)
            self._scatter_item = None

        nclasses = len(self.class_model)
        pens = [pen(self.colors[i]) for i in range(nclasses)]

        self._scatter_item = pg.ScatterPlotItem(
            self.data[:, 0],
            self.data[:, 1] if self.hasAttr2 else np.zeros(self.data.shape[0]),
            symbol="+",
            pen=[pens[int(ci)] for ci in self.data[:, 2]]
        )

        self.plot.addItem(self._scatter_item)

    def _attr_name_changed(self):
        self.plot.getAxis("bottom").setLabel(self.attr1)
        self.plot.getAxis("left").setLabel(self.attr2)
        self.invalidate()

    def invalidate(self):
        self.commit()

    def commit(self):
        if len(self.data) == 0:
            self.send("Data", None)
            return
        if self.hasAttr2:
            X, Y = self.data[:, :2], self.data[:, 2]
            attrs = (Orange.data.ContinuousVariable(self.attr1),
                     Orange.data.ContinuousVariable(self.attr2))
        else:
            X, Y = self.data[:, np.newaxis, 0], self.data[:, 2]
            attrs = (Orange.data.ContinuousVariable(self.attr1),)
        if len(np.unique(Y)) >= 2:
            domain = Orange.data.Domain(
                attrs,
                Orange.data.DiscreteVariable(
                    "Class", values=list(self.class_model))
            )
            data = Orange.data.Table.from_numpy(domain, X, Y)
        else:
            domain = Orange.data.Domain(attrs)
            data = Orange.data.Table.from_numpy(domain, X)
        data.name = self.table_name
        self.send("Data", data)

    def sizeHint(self):
        sh = super().sizeHint()
        return sh.expandedTo(QSize(1200, 800))

    def onDeleteWidget(self):
        self.plot.clear()

    def send_report(self):
        if self.data is None:
            return
        settings = []
        if self.attr1 != "x" or self.attr2 != "y":
            settings += [("Axis x", self.attr1), ("Axis y", self.attr2)]
        settings += [("Number of points", len(self.data))]
        self.report_items("Painted data", settings)
        self.report_plot()
Пример #52
0
    def __init__(self):
        super().__init__()
        self.__pending_selection = self.selected_rows

        # A kingdom for a save_state/restore_state
        self.col_clustering = enum_get(
            Clustering, self.col_clustering_method, Clustering.None_)
        self.row_clustering = enum_get(
            Clustering, self.row_clustering_method, Clustering.None_)

        @self.settingsAboutToBePacked.connect
        def _():
            self.col_clustering_method = self.col_clustering.name
            self.row_clustering_method = self.row_clustering.name

        self.keep_aspect = False

        #: The original data with all features (retained to
        #: preserve the domain on the output)
        self.input_data = None
        #: The effective data striped of discrete features, and often
        #: merged using k-means
        self.data = None
        self.effective_data = None
        #: kmeans model used to merge rows of input_data
        self.kmeans_model = None
        #: merge indices derived from kmeans
        #: a list (len==k) of int ndarray where the i-th item contains
        #: the indices which merge the input_data into the heatmap row i
        self.merge_indices = None
        self.parts: Optional[Parts] = None
        self.__rows_cache = {}
        self.__columns_cache = {}

        self.info.set_input_summary(self.info.NoInput)
        self.info.set_output_summary(self.info.NoOutput)

        # GUI definition
        colorbox = gui.vBox(self.controlArea, "Color")

        self.color_map_widget = cmw = ColorGradientSelection(
            thresholds=(self.threshold_low, self.threshold_high),
        )
        model = itemmodels.ContinuousPalettesModel(parent=self)
        cmw.setModel(model)
        idx = cmw.findData(self.palette_name, model.KeyRole)
        if idx != -1:
            cmw.setCurrentIndex(idx)

        cmw.activated.connect(self.update_color_schema)

        def _set_thresholds(low, high):
            self.threshold_low, self.threshold_high = low, high
            self.update_color_schema()
        cmw.thresholdsChanged.connect(_set_thresholds)
        colorbox.layout().addWidget(self.color_map_widget)

        mergebox = gui.vBox(self.controlArea, "Merge",)
        gui.checkBox(mergebox, self, "merge_kmeans", "Merge by k-means",
                     callback=self.__update_row_clustering)
        ibox = gui.indentedBox(mergebox)
        gui.spin(ibox, self, "merge_kmeans_k", minv=5, maxv=500,
                 label="Clusters:", keyboardTracking=False,
                 callbackOnReturn=True, callback=self.update_merge)

        cluster_box = gui.vBox(self.controlArea, "Clustering")
        # Row clustering
        self.row_cluster_cb = cb = ComboBox()
        cb.setModel(create_list_model(ClusteringModelData, self))
        cbselect(cb, self.row_clustering, ClusteringRole)
        self.connect_control(
            "row_clustering",
            lambda value, cb=cb: cbselect(cb, value, ClusteringRole)
        )
        @cb.activated.connect
        def _(idx, cb=cb):
            self.set_row_clustering(cb.itemData(idx, ClusteringRole))

        # Column clustering
        self.col_cluster_cb = cb = ComboBox()
        cb.setModel(create_list_model(ClusteringModelData, self))
        cbselect(cb, self.col_clustering, ClusteringRole)
        self.connect_control(
            "col_clustering",
            lambda value, cb=cb: cbselect(cb, value, ClusteringRole)
        )
        @cb.activated.connect
        def _(idx, cb=cb):
            self.set_col_clustering(cb.itemData(idx, ClusteringRole))

        form = QFormLayout(
            labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow,
        )
        form.addRow("Rows:", self.row_cluster_cb)
        form.addRow("Columns:", self.col_cluster_cb)
        cluster_box.layout().addLayout(form)
        box = gui.vBox(self.controlArea, "Split By")

        self.row_split_model = DomainModel(
            placeholder="(None)",
            valid_types=(Orange.data.DiscreteVariable,),
            parent=self,
        )
        self.row_split_cb = cb = ComboBoxSearch(
            enabled=not self.merge_kmeans,
            sizeAdjustPolicy=ComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=14,
            toolTip="Split the heatmap vertically by a categorical column"
        )
        self.row_split_cb.setModel(self.row_split_model)
        self.connect_control(
            "split_by_var", lambda value, cb=cb: cbselect(cb, value)
        )
        self.connect_control(
            "merge_kmeans", self.row_split_cb.setDisabled
        )
        self.split_by_var = None

        self.row_split_cb.activated.connect(
            self.__on_split_rows_activated
        )
        box.layout().addWidget(self.row_split_cb)

        box = gui.vBox(self.controlArea, 'Annotation && Legends')

        gui.checkBox(box, self, 'legend', 'Show legend',
                     callback=self.update_legend)

        gui.checkBox(box, self, 'averages', 'Stripes with averages',
                     callback=self.update_averages_stripe)
        annotbox = QGroupBox("Row Annotations", flat=True)
        form = QFormLayout(
            annotbox,
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )
        self.annotation_model = DomainModel(placeholder="(None)")
        self.annotation_text_cb = ComboBoxSearch(
            minimumContentsLength=12,
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength
        )
        self.annotation_text_cb.setModel(self.annotation_model)
        self.annotation_text_cb.activated.connect(self.set_annotation_var)
        self.connect_control("annotation_var", self.annotation_var_changed)

        self.row_side_color_model = DomainModel(
            order=(DomainModel.CLASSES, DomainModel.Separator,
                   DomainModel.METAS),
            placeholder="(None)", valid_types=DomainModel.PRIMITIVE,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled,
            parent=self,
        )
        self.row_side_color_cb = ComboBoxSearch(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength,
            minimumContentsLength=12
        )
        self.row_side_color_cb.setModel(self.row_side_color_model)
        self.row_side_color_cb.activated.connect(self.set_annotation_color_var)
        self.connect_control("annotation_color_var", self.annotation_color_var_changed)
        form.addRow("Text", self.annotation_text_cb)
        form.addRow("Color", self.row_side_color_cb)
        box.layout().addWidget(annotbox)
        posbox = gui.vBox(box, "Column Labels Position", addSpace=False)
        posbox.setFlat(True)
        cb = gui.comboBox(
            posbox, self, "column_label_pos",
            callback=self.update_column_annotations)
        cb.setModel(create_list_model(ColumnLabelsPosData, parent=self))
        cb.setCurrentIndex(self.column_label_pos)
        gui.checkBox(self.controlArea, self, "keep_aspect",
                     "Keep aspect ratio", box="Resize",
                     callback=self.__aspect_mode_changed)

        gui.rubber(self.controlArea)
        gui.auto_send(self.controlArea, self, "auto_commit")

        # Scene with heatmap
        class HeatmapScene(QGraphicsScene):
            widget: Optional[HeatmapGridWidget] = None

        self.scene = self.scene = HeatmapScene(parent=self)
        self.view = GraphicsView(
            self.scene,
            verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOn,
            viewportUpdateMode=QGraphicsView.FullViewportUpdate,
            widgetResizable=True,
        )
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(
            self._on_view_context_menu
        )
        self.mainArea.layout().addWidget(self.view)
        self.selected_rows = []
        self.__font_inc = QAction(
            "Increase Font", self, shortcut=QKeySequence("ctrl+>"))
        self.__font_dec = QAction(
            "Decrease Font", self, shortcut=QKeySequence("ctrl+<"))
        self.__font_inc.triggered.connect(lambda: self.__adjust_font_size(1))
        self.__font_dec.triggered.connect(lambda: self.__adjust_font_size(-1))
        if hasattr(QAction, "setShortcutVisibleInContextMenu"):
            apply_all(
                [self.__font_inc, self.__font_dec],
                lambda a: a.setShortcutVisibleInContextMenu(True)
            )
        self.addActions([self.__font_inc, self.__font_dec])
Пример #53
0
 def makeaction(text, parent, data=None, **kwargs):
     action = QAction(text, parent, **kwargs)
     if data is not None:
         action.setData(data)
     return action
Пример #54
0
class VariableEditor(QWidget):
    """An editor widget for a variable.

    Can edit the variable name, and its attributes dictionary.

    """
    variable_changed = Signal()

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.var = None
        self.setup_gui()

    def setup_gui(self):
        layout = QVBoxLayout()
        self.setLayout(layout)

        self.main_form = QFormLayout()
        self.main_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
        layout.addLayout(self.main_form)

        self._setup_gui_name()
        self._setup_gui_labels()

    def _setup_gui_name(self):
        self.name_edit = QLineEdit()
        self.main_form.addRow("Name:", self.name_edit)
        self.name_edit.editingFinished.connect(self.on_name_changed)

    def _setup_gui_labels(self):
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        vlayout.setSpacing(1)

        self.labels_edit = QTreeView()
        self.labels_edit.setEditTriggers(QTreeView.CurrentChanged)
        self.labels_edit.setRootIsDecorated(False)

        self.labels_model = DictItemsModel()
        self.labels_edit.setModel(self.labels_model)

        self.labels_edit.selectionModel().selectionChanged.connect(
            self.on_label_selection_changed)

        # Necessary signals to know when the labels change
        self.labels_model.dataChanged.connect(self.on_labels_changed)
        self.labels_model.rowsInserted.connect(self.on_labels_changed)
        self.labels_model.rowsRemoved.connect(self.on_labels_changed)

        vlayout.addWidget(self.labels_edit)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        hlayout.setSpacing(1)
        self.add_label_action = QAction(
            "+", self,
            toolTip="Add a new label.",
            triggered=self.on_add_label,
            enabled=False,
            shortcut=QKeySequence(QKeySequence.New))

        self.remove_label_action = QAction(
            unicodedata.lookup("MINUS SIGN"), self,
            toolTip="Remove selected label.",
            triggered=self.on_remove_label,
            enabled=False,
            shortcut=QKeySequence(QKeySequence.Delete))

        button_size = gui.toolButtonSizeHint()
        button_size = QSize(button_size, button_size)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.add_label_action)
        hlayout.addWidget(button)

        button = QToolButton(self)
        button.setFixedSize(button_size)
        button.setDefaultAction(self.remove_label_action)
        hlayout.addWidget(button)
        hlayout.addStretch(10)
        vlayout.addLayout(hlayout)

        self.main_form.addRow("Labels:", vlayout)

    def set_data(self, var):
        """Set the variable to edit.
        """
        self.clear()
        self.var = var

        if var is not None:
            self.name_edit.setText(var.name)
            self.labels_model.set_dict(dict(var.attributes))
            self.add_label_action.setEnabled(True)
        else:
            self.add_label_action.setEnabled(False)
            self.remove_label_action.setEnabled(False)

    def get_data(self):
        """Retrieve the modified variable.
        """
        name = str(self.name_edit.text())
        labels = self.labels_model.get_dict()

        # Is the variable actually changed.
        if self.var is not None and not self.is_same():
            var = type(self.var)(name)
            var.attributes.update(labels)
            self.var = var
        else:
            var = self.var

        return var

    def is_same(self):
        """Is the current model state the same as the input.
        """
        name = str(self.name_edit.text())
        labels = self.labels_model.get_dict()

        return (self.var is not None and name == self.var.name and
                labels == self.var.attributes)

    def clear(self):
        """Clear the editor state.
        """
        self.var = None
        self.name_edit.setText("")
        self.labels_model.set_dict({})

    def maybe_commit(self):
        if not self.is_same():
            self.commit()

    def commit(self):
        """Emit a ``variable_changed()`` signal.
        """
        self.variable_changed.emit()

    @Slot()
    def on_name_changed(self):
        self.maybe_commit()

    @Slot()
    def on_labels_changed(self, *args):
        self.maybe_commit()

    @Slot()
    def on_add_label(self):
        self.labels_model.appendRow([QStandardItem(""), QStandardItem("")])
        row = self.labels_model.rowCount() - 1
        index = self.labels_model.index(row, 0)
        self.labels_edit.edit(index)

    @Slot()
    def on_remove_label(self):
        rows = self.labels_edit.selectionModel().selectedRows()
        if rows:
            row = rows[0]
            self.labels_model.removeRow(row.row())

    @Slot()
    def on_label_selection_changed(self):
        selected = self.labels_edit.selectionModel().selectedRows()
        self.remove_label_action.setEnabled(bool(len(selected)))
Пример #55
0
    def __init__(self):
        super().__init__()

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWScatterPlotGraph(self, box, "ScatterPlot")
        box.layout().addWidget(self.graph.plot_widget)
        plot = self.graph.plot_widget

        axispen = QPen(self.palette().color(QPalette.Text))
        axis = plot.getAxis("bottom")
        axis.setPen(axispen)

        axis = plot.getAxis("left")
        axis.setPen(axispen)

        self.data = None  # Orange.data.Table
        self.subset_data = None  # Orange.data.Table
        self.data_metas_X = None  # self.data, where primitive metas are moved to X
        self.sql_data = None  # Orange.data.sql.table.SqlTable
        self.attribute_selection_list = None  # list of Orange.data.Variable
        self.__timer = QTimer(self, interval=1200)
        self.__timer.timeout.connect(self.add_data)

        common_options = dict(
            labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True,
            valueType=str)
        box = gui.vBox(self.controlArea, "Axis Data")
        dmod = DomainModel
        self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(
            box, self, "attr_x", label="Axis x:", callback=self.update_attr,
            model=self.xy_model, **common_options)
        self.cb_attr_y = gui.comboBox(
            box, self, "attr_y", label="Axis y:", callback=self.update_attr,
            model=self.xy_model, **common_options)

        vizrank_box = gui.hBox(box)
        gui.separator(vizrank_box, width=common_options["labelWidth"])
        self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank(
            vizrank_box, self, "Find Informative Projections", self.set_attr)

        gui.separator(box)

        gui.valueSlider(
            box, self, value='graph.jitter_size', label='Jittering: ',
            values=self.jitter_sizes, callback=self.reset_graph_data,
            labelFormat=lambda x:
            "None" if x == 0 else ("%.1f %%" if x < 1 else "%d %%") % x)
        gui.checkBox(
            gui.indentedBox(box), self, 'graph.jitter_continuous',
            'Jitter numeric values', callback=self.reset_graph_data)

        self.sampling = gui.auto_commit(
            self.controlArea, self, "auto_sample", "Sample", box="Sampling",
            callback=self.switch_sampling, commit=lambda: self.add_data(1))
        self.sampling.setVisible(False)

        g = self.graph.gui
        g.point_properties_box(self.controlArea)
        self.models = [self.xy_model] + g.points_models

        box = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([g.ShowLegend, g.ShowGridLines], box)
        gui.checkBox(
            box, self, value='graph.tooltip_shows_all',
            label='Show all data on mouse hover')
        self.cb_class_density = gui.checkBox(
            box, self, value='graph.class_density', label='Show class density',
            callback=self.update_density)
        self.cb_reg_line = gui.checkBox(
            box, self, value='graph.show_reg_line',
            label='Show regression line', callback=self.update_regression_line)
        gui.checkBox(
            box, self, 'graph.label_only_selected',
            'Label only selected points', callback=self.graph.update_labels)

        self.zoom_select_toolbar = g.zoom_select_toolbar(
            gui.vBox(self.controlArea, "Zoom/Select"), nomargin=True,
            buttons=[g.StateButtonsBegin, g.SimpleSelect, g.Pan, g.Zoom,
                     g.StateButtonsEnd, g.ZoomReset]
        )
        buttons = self.zoom_select_toolbar.buttons
        buttons[g.Zoom].clicked.connect(self.graph.zoom_button_clicked)
        buttons[g.Pan].clicked.connect(self.graph.pan_button_clicked)
        buttons[g.SimpleSelect].clicked.connect(self.graph.select_button_clicked)
        buttons[g.ZoomReset].clicked.connect(self.graph.reset_button_clicked)
        self.controlArea.layout().addStretch(100)
        self.icons = gui.attributeIconDict

        p = self.graph.plot_widget.palette()
        self.graph.set_palette(p)

        gui.auto_commit(self.controlArea, self, "auto_send_selection",
                        "Send Selection", "Send Automatically")

        def zoom(s):
            """Zoom in/out by factor `s`."""
            viewbox = plot.getViewBox()
            # scaleBy scales the view's bounds (the axis range)
            viewbox.scaleBy((1 / s, 1 / s))

        def fit_to_view():
            viewbox = plot.getViewBox()
            viewbox.autoRange()

        zoom_in = QAction(
            "Zoom in", self, triggered=lambda: zoom(1.25)
        )
        zoom_in.setShortcuts([QKeySequence(QKeySequence.ZoomIn),
                              QKeySequence(self.tr("Ctrl+="))])
        zoom_out = QAction(
            "Zoom out", self, shortcut=QKeySequence.ZoomOut,
            triggered=lambda: zoom(1 / 1.25)
        )
        zoom_fit = QAction(
            "Fit in view", self,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
            triggered=fit_to_view
        )
        self.addActions([zoom_in, zoom_out, zoom_fit])
Пример #56
0
    def __init__(self):
        super().__init__()
        #: widget's runtime state
        self.__state = State.NoState
        self.base_corpus = None
        self.corpus = None
        self.n_text_categories = 0
        self.n_text_data = 0
        self.skipped_documents = []
        self.is_conllu = False
        self.tokens = None
        self.pos = None
        self.ner = None

        self.__invalidated = False
        self.__pendingTask = None

        layout = QGridLayout()
        layout.setSpacing(4)
        gui.widgetBox(self.controlArea, orientation=layout, box='Source')
        source_box = gui.radioButtons(None,
                                      self,
                                      "source",
                                      box=True,
                                      callback=self.start,
                                      addToLayout=False)
        rb_button = gui.appendRadioButton(source_box,
                                          "Folder:",
                                          addToLayout=False)
        layout.addWidget(rb_button, 0, 0, Qt.AlignVCenter)

        box = gui.hBox(None, addToLayout=False, margin=0)
        box.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)

        self.recent_cb = QComboBox(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=16,
            acceptDrops=True)
        self.recent_cb.installEventFilter(self)
        self.recent_cb.activated[int].connect(self.__onRecentActivated)

        browseaction = QAction(
            "Open/Load Documents",
            self,
            iconText="\N{HORIZONTAL ELLIPSIS}",
            icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),
            toolTip="Select a folder from which to load the documents")
        browseaction.triggered.connect(self.__runOpenDialog)
        reloadaction = QAction("Reload",
                               self,
                               icon=self.style().standardIcon(
                                   QStyle.SP_BrowserReload),
                               toolTip="Reload current document set")
        reloadaction.triggered.connect(self.reload)
        self.__actions = namespace(
            browse=browseaction,
            reload=reloadaction,
        )

        browsebutton = QPushButton(
            browseaction.iconText(),
            icon=browseaction.icon(),
            toolTip=browseaction.toolTip(),
            clicked=browseaction.trigger,
            default=False,
            autoDefault=False,
        )
        reloadbutton = QPushButton(
            reloadaction.iconText(),
            icon=reloadaction.icon(),
            clicked=reloadaction.trigger,
            default=False,
            autoDefault=False,
        )
        box.layout().addWidget(self.recent_cb)
        layout.addWidget(box, 0, 1)
        layout.addWidget(browsebutton, 0, 2)
        layout.addWidget(reloadbutton, 0, 3)

        rb_button = gui.appendRadioButton(source_box,
                                          "URL:",
                                          addToLayout=False)
        layout.addWidget(rb_button, 3, 0, Qt.AlignVCenter)

        self.url_combo = url_combo = QComboBox()
        url_model = PyListModel()
        url_model.wrap(self.recent_urls)
        url_combo.setLineEdit(LineEditSelectOnFocus())
        url_combo.setModel(url_model)
        url_combo.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
        url_combo.setEditable(True)
        url_combo.setInsertPolicy(url_combo.InsertAtTop)
        url_edit = url_combo.lineEdit()
        m = url_edit.textMargins()
        url_edit.setTextMargins(m.left() + 5, m.top(), m.right(), m.bottom())
        layout.addWidget(url_combo, 3, 1, 1, 3)
        url_combo.activated.connect(self._url_set)
        # whit completer we set that combo box is case sensitive when
        # matching the history
        completer = QCompleter()
        completer.setCaseSensitivity(Qt.CaseSensitive)
        url_combo.setCompleter(completer)

        self.addActions([browseaction, reloadaction])

        reloadaction.changed.connect(
            lambda: reloadbutton.setEnabled(reloadaction.isEnabled()))

        box = gui.hBox(self.controlArea, "Conllu import options")
        gui.checkBox(box, self, "lemma_cb", "Lemma", callback=self.commit)
        gui.checkBox(box, self, "pos_cb", "POS tags", callback=self.commit)
        gui.checkBox(box, self, "ner_cb", "NER", callback=self.commit)
        self.controlArea.layout().addWidget(box)

        box = gui.vBox(self.controlArea, "Info")
        self.infostack = QStackedWidget()

        self.info_area = QLabel(text="No document set selected", wordWrap=True)
        self.progress_widget = QProgressBar(minimum=0, maximum=100)
        self.cancel_button = QPushButton(
            "Cancel",
            icon=self.style().standardIcon(QStyle.SP_DialogCancelButton),
            default=False,
            autoDefault=False,
        )
        self.cancel_button.clicked.connect(self.cancel)

        w = QWidget()
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)

        hlayout.addWidget(self.progress_widget)
        hlayout.addWidget(self.cancel_button)
        vlayout.addLayout(hlayout)

        self.pathlabel = TextLabel()
        self.pathlabel.setTextElideMode(Qt.ElideMiddle)
        self.pathlabel.setAttribute(Qt.WA_MacSmallSize)

        vlayout.addWidget(self.pathlabel)
        w.setLayout(vlayout)

        self.infostack.addWidget(self.info_area)
        self.infostack.addWidget(w)

        box.layout().addWidget(self.infostack)

        self.__initRecentItemsModel()
        self.__invalidated = True
        self.__executor = ThreadExecutor(self)

        QApplication.postEvent(self, QEvent(RuntimeEvent.Init))
Пример #57
0
    def __init__(self):
        super().__init__()

        for name in self.signal_names:
            setattr(self, name, {})

        for s in self.libraryListSource:
            s.flags = 0

        self._cachedDocuments = {}

        self.infoBox = gui.vBox(self.controlArea, 'Info')
        gui.label(
            self.infoBox, self,
            "<p>Execute python script.</p><p>Input variables:<ul><li> " +
            "<li>".join(map("in_{0}, in_{0}s".format, self.signal_names)) +
            "</ul></p><p>Output variables:<ul><li>" +
            "<li>".join(map("out_{0}".format, self.signal_names)) +
            "</ul></p>"
        )

        self.libraryList = itemmodels.PyListModel(
            [], self,
            flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable)

        self.libraryList.wrap(self.libraryListSource)

        self.controlBox = gui.vBox(self.controlArea, 'Library')
        self.controlBox.layout().setSpacing(1)

        self.libraryView = QListView(
            editTriggers=QListView.DoubleClicked |
            QListView.EditKeyPressed,
            sizePolicy=QSizePolicy(QSizePolicy.Ignored,
                                   QSizePolicy.Preferred)
        )
        self.libraryView.setItemDelegate(ScriptItemDelegate(self))
        self.libraryView.setModel(self.libraryList)

        self.libraryView.selectionModel().selectionChanged.connect(
            self.onSelectedScriptChanged
        )
        self.controlBox.layout().addWidget(self.libraryView)

        w = itemmodels.ModelActionsWidget()

        self.addNewScriptAction = action = QAction("+", self)
        action.setToolTip("Add a new script to the library")
        action.triggered.connect(self.onAddScript)
        w.addAction(action)

        action = QAction(unicodedata.lookup("MINUS SIGN"), self)
        action.setToolTip("Remove script from library")
        action.triggered.connect(self.onRemoveScript)
        w.addAction(action)

        action = QAction("Update", self)
        action.setToolTip("Save changes in the editor to library")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.triggered.connect(self.commitChangesToLibrary)
        w.addAction(action)

        action = QAction("More", self, toolTip="More actions")

        new_from_file = QAction("Import Script from File", self)
        save_to_file = QAction("Save Selected Script to File", self)
        restore_saved = QAction("Undo Changes to Selected Script", self)
        save_to_file.setShortcut(QKeySequence(QKeySequence.SaveAs))

        new_from_file.triggered.connect(self.onAddScriptFromFile)
        save_to_file.triggered.connect(self.saveScript)
        restore_saved.triggered.connect(self.restoreSaved)

        menu = QMenu(w)
        menu.addAction(new_from_file)
        menu.addAction(save_to_file)
        menu.addAction(restore_saved)
        action.setMenu(menu)
        button = w.addAction(action)
        button.setPopupMode(QToolButton.InstantPopup)

        w.layout().setSpacing(1)

        self.controlBox.layout().addWidget(w)

        self.execute_button = gui.button(self.controlArea, self, 'Run', callback=self.commit)

        self.splitCanvas = QSplitter(Qt.Vertical, self.mainArea)
        self.mainArea.layout().addWidget(self.splitCanvas)

        self.defaultFont = defaultFont = \
            "Monaco" if sys.platform == "darwin" else "Courier"

        self.textBox = gui.vBox(self, 'Python Script')
        self.splitCanvas.addWidget(self.textBox)
        self.text = PythonScriptEditor(self)
        self.textBox.layout().addWidget(self.text)

        self.textBox.setAlignment(Qt.AlignVCenter)
        self.text.setTabStopWidth(4)

        self.text.modificationChanged[bool].connect(self.onModificationChanged)

        self.saveAction = action = QAction("&Save", self.text)
        action.setToolTip("Save script to file")
        action.setShortcut(QKeySequence(QKeySequence.Save))
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.saveScript)

        self.consoleBox = gui.vBox(self, 'Console')
        self.splitCanvas.addWidget(self.consoleBox)
        self.console = PythonConsole({}, self)
        self.consoleBox.layout().addWidget(self.console)
        self.console.document().setDefaultFont(QFont(defaultFont))
        self.consoleBox.setAlignment(Qt.AlignBottom)
        self.console.setTabStopWidth(4)

        select_row(self.libraryView, self.currentScriptIndex)

        self.restoreScriptText()

        self.splitCanvas.setSizes([2, 1])
        if self.splitterState is not None:
            self.splitCanvas.restoreState(QByteArray(self.splitterState))

        self.splitCanvas.splitterMoved[int, int].connect(self.onSpliterMoved)
        self.controlArea.layout().addStretch(1)
        self.resize(800, 600)
Пример #58
0
 def makeaction(text, parent, data=None, **kwargs):
     # type: (str, QObject, Any, Any) -> QAction
     action = QAction(text, parent, **kwargs)
     if data is not None:
         action.setData(data)
     return action
Пример #59
0
    def __init__(self):
        super().__init__()
        self.data = None  # type: Optional[Orange.data.Table]
        self._counts = None  # type: Optional[np.ndarray]

        box = gui.widgetBox(self.controlArea, "Info")
        self._info = QLabel(box, wordWrap=True)
        self._info.setText("No data in input\n")

        box.layout().addWidget(self._info)

        box = gui.widgetBox(self.controlArea, "Filter Type")
        rbg = QButtonGroup(box, exclusive=True)
        for id_ in [Cells, Genes, Data]:
            name, _, tip = FilterInfo[id_]
            b = QRadioButton(name,
                             toolTip=tip,
                             checked=id_ == self.selected_filter_type)
            box.layout().addWidget(b)
            rbg.addButton(b, id_)
        rbg.buttonClicked[int].connect(self.set_filter_type)

        box = gui.widgetBox(self.controlArea, "View")
        self._showpoints = gui.checkBox(box,
                                        self,
                                        "display_dotplot",
                                        "Show data points",
                                        callback=self._update_dotplot)
        form = QFormLayout(labelAlignment=Qt.AlignLeft,
                           formAlignment=Qt.AlignLeft,
                           fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow)
        self._filter_box = box = gui.widgetBox(
            self.controlArea, "Filter", orientation=form)  # type: QGroupBox

        self.threshold_stacks = (
            QStackedWidget(enabled=self.limit_lower_enabled),
            QStackedWidget(enabled=self.limit_upper_enabled),
        )
        finfo = np.finfo(np.float64)
        for filter_ in [Cells, Genes, Data]:
            if filter_ in {Cells, Genes}:
                minimum = 0.0
                ndecimals = 1
            else:
                minimum = finfo.min
                ndecimals = 3
            spinlower = QDoubleSpinBox(
                self,
                minimum=minimum,
                maximum=finfo.max,
                decimals=ndecimals,
                keyboardTracking=False,
            )
            spinupper = QDoubleSpinBox(
                self,
                minimum=minimum,
                maximum=finfo.max,
                decimals=ndecimals,
                keyboardTracking=False,
            )

            lower, upper = self.thresholds[filter_]

            spinlower.setValue(lower)
            spinupper.setValue(upper)

            self.threshold_stacks[0].addWidget(spinlower)
            self.threshold_stacks[1].addWidget(spinupper)

            spinlower.valueChanged.connect(self._limitchanged)
            spinupper.valueChanged.connect(self._limitchanged)

        self.threshold_stacks[0].setCurrentIndex(self.selected_filter_type)
        self.threshold_stacks[1].setCurrentIndex(self.selected_filter_type)

        self.limit_lower_enabled_cb = cb = QCheckBox(
            "Min", checked=self.limit_lower_enabled)
        cb.toggled.connect(self.set_lower_limit_enabled)
        cb.setAttribute(Qt.WA_LayoutUsesWidgetRect, True)
        form.addRow(cb, self.threshold_stacks[0])

        self.limit_upper_enabled_cb = cb = QCheckBox(
            "Max", checked=self.limit_upper_enabled)
        cb.toggled.connect(self.set_upper_limit_enabled)
        cb.setAttribute(Qt.WA_LayoutUsesWidgetRect, True)
        form.addRow(cb, self.threshold_stacks[1])

        self.controlArea.layout().addStretch(10)

        gui.auto_commit(self.controlArea, self, "auto_commit", "Commit")

        self._view = pg.GraphicsView()
        self._view.enableMouse(False)
        self._view.setAntialiasing(True)
        self._plot = plot = ViolinPlot()
        self._plot.setDataPointsVisible(self.display_dotplot)
        self._plot.setSelectionMode(
            (ViolinPlot.Low if self.limit_lower_enabled else 0)
            | (ViolinPlot.High if self.limit_upper_enabled else 0))
        self._plot.selectionEdited.connect(self._limitchanged_plot)
        self._view.setCentralWidget(self._plot)
        self._plot.setTitle("Detected genes")

        left = self._plot.getAxis("left")  # type: pg.AxisItem
        left.setLabel("Detected genes")
        bottom = self._plot.getAxis("bottom")  # type: pg.AxisItem
        bottom.hide()
        plot.setMouseEnabled(False, False)
        plot.hideButtons()
        self.mainArea.layout().addWidget(self._view)

        # Coalescing commit timer
        self._committimer = QTimer(self, singleShot=True)
        self._committimer.timeout.connect(self.commit)

        self.addAction(
            QAction("Select All",
                    self,
                    shortcut=QKeySequence.SelectAll,
                    triggered=self._select_all))
Пример #60
0
    def __init__(self, parent):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)
        SelectionGroupMixin.__init__(self)
        ImageColorSettingMixin.__init__(self)
        ImageZoomMixin.__init__(self)
        ConcurrentMixin.__init__(self)
        self.parent = parent

        self.selection_type = SELECTMANY
        self.saving_enabled = True
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        self.data_valid_positions = None

        self.plotview = pg.GraphicsLayoutWidget()
        self.plot = pg.PlotItem(background="w",
                                viewBox=InteractiveViewBox(self))
        self.plotview.addItem(self.plot)

        self.legend = ImageColorLegend()
        self.plotview.addItem(self.legend)

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.vis_img = pg.ImageItem()
        self.vis_img.setOpts(axisOrder='row-major')
        self.plot.vb.setAspectLocked()
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("Menu", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        # prepare interface according to the new context
        self.parent.contextAboutToBeOpened.connect(
            lambda x: self.init_interface_data(x[0]))

        actions = []

        self.add_zoom_actions(view_menu)

        select_square = QAction(
            "Select (square)",
            self,
            triggered=self.plot.vb.set_mode_select_square,
        )
        select_square.setShortcuts([Qt.Key_S])
        select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_square)

        select_polygon = QAction(
            "Select (polygon)",
            self,
            triggered=self.plot.vb.set_mode_select_polygon,
        )
        select_polygon.setShortcuts([Qt.Key_P])
        select_polygon.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_polygon)

        if self.saving_enabled:
            save_graph = QAction(
                "Save graph",
                self,
                triggered=self.save_graph,
            )
            save_graph.setShortcuts(
                [QKeySequence(Qt.ControlModifier | Qt.Key_I)])
            actions.append(save_graph)

        view_menu.addActions(actions)
        self.addActions(actions)

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        box.setFocusProxy(self.cb_attr_x)

        box.layout().addWidget(self.color_settings_box())

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}