|
27 | 27 |
|
28 | 28 | import os |
29 | 29 |
|
30 | | -from Orange.data import ContinuousVariable, DiscreteVariable |
| 30 | +import unicodedata |
| 31 | +from functools import reduce |
| 32 | +from operator import itemgetter |
| 33 | + |
| 34 | +from Orange.data import ContinuousVariable, DiscreteVariable, Variable |
31 | 35 | from Orange.widgets import gui |
| 36 | +from Orange.widgets.utils import itemmodels |
| 37 | +from Orange.widgets.utils.listfilter import variables_filter |
32 | 38 | from Orange.widgets.utils.itemmodels import DomainModel |
33 | 39 |
|
34 | | -from AnyQt.QtWidgets import QWidget, QToolButton, QVBoxLayout, QHBoxLayout, QMenu, QAction |
35 | | -from AnyQt.QtGui import QIcon |
36 | | -from AnyQt.QtCore import Qt, pyqtSignal |
| 40 | +from AnyQt.QtWidgets import QWidget, QToolButton, QVBoxLayout, QHBoxLayout, QMenu, QAction,\ |
| 41 | + QDialog, QSizePolicy, QPushButton, QListView |
| 42 | +from AnyQt.QtGui import QIcon, QKeySequence |
| 43 | +from AnyQt.QtCore import Qt, pyqtSignal, QPoint, QSize |
37 | 44 |
|
38 | 45 | from .owconstants import NOTHING, ZOOMING, SELECT, SELECT_POLYGON, PANNING, SELECTION_ADD,\ |
39 | 46 | SELECTION_REMOVE, SELECTION_TOGGLE, SELECTION_REPLACE |
40 | 47 |
|
41 | 48 |
|
| 49 | +SIZE_POLICY_ADAPTING = (QSizePolicy.Expanding, QSizePolicy.Ignored) |
| 50 | +SIZE_POLICY_FIXED = (QSizePolicy.Minimum, QSizePolicy.Maximum) |
| 51 | + |
| 52 | + |
| 53 | +class AddVariablesDialog(QDialog): |
| 54 | + def __init__(self, master, model): |
| 55 | + QDialog.__init__(self) |
| 56 | + |
| 57 | + self.master = master |
| 58 | + |
| 59 | + self.setWindowFlags(Qt.Tool) |
| 60 | + self.setLayout(QVBoxLayout()) |
| 61 | + self.setWindowTitle("Hidden Axes") |
| 62 | + |
| 63 | + btns_area = gui.widgetBox( |
| 64 | + self, addSpace=0, spacing=9, orientation=Qt.Horizontal, |
| 65 | + sizePolicy=QSizePolicy(*SIZE_POLICY_FIXED) |
| 66 | + ) |
| 67 | + self.btn_add = QPushButton( |
| 68 | + "Add", autoDefault=False, sizePolicy=QSizePolicy(*SIZE_POLICY_FIXED) |
| 69 | + ) |
| 70 | + self.btn_add.clicked.connect(self._add) |
| 71 | + self.btn_cancel = QPushButton( |
| 72 | + "Cancel", autoDefault=False, sizePolicy=QSizePolicy(*SIZE_POLICY_FIXED) |
| 73 | + ) |
| 74 | + self.btn_cancel.clicked.connect(self._cancel) |
| 75 | + |
| 76 | + btns_area.layout().addWidget(self.btn_add) |
| 77 | + btns_area.layout().addWidget(self.btn_cancel) |
| 78 | + |
| 79 | + filter_edit, view = variables_filter(model=model) |
| 80 | + self.view_other = view |
| 81 | + view.setMinimumSize(QSize(30, 60)) |
| 82 | + view.setSizePolicy(*SIZE_POLICY_ADAPTING) |
| 83 | + view.viewport().setAcceptDrops(True) |
| 84 | + |
| 85 | + self.layout().addWidget(filter_edit) |
| 86 | + self.layout().addWidget(view) |
| 87 | + self.layout().addWidget(btns_area) |
| 88 | + |
| 89 | + master = self.master |
| 90 | + box = master.box |
| 91 | + master.master.setEnabled(False) |
| 92 | + self.move(box.mapToGlobal(QPoint(0, box.pos().y() + box.height()))) |
| 93 | + self.setFixedWidth(master.master.controlArea.width()) |
| 94 | + self.setMinimumHeight(300) |
| 95 | + self.show() |
| 96 | + self.raise_() |
| 97 | + self.activateWindow() |
| 98 | + |
| 99 | + def _cancel(self): |
| 100 | + self.closeEvent(None) |
| 101 | + |
| 102 | + def _add(self): |
| 103 | + self.add_variables() |
| 104 | + self.closeEvent(None) |
| 105 | + |
| 106 | + def closeEvent(self, QCloseEvent): |
| 107 | + self.master.master.setEnabled(True) |
| 108 | + super().closeEvent(QCloseEvent) |
| 109 | + |
| 110 | + def keyPressEvent(self, e): |
| 111 | + if e.key() == Qt.Key_Escape: |
| 112 | + self.closeEvent(None) |
| 113 | + elif e.key() in [Qt.Key_Return, Qt.Key_Enter]: |
| 114 | + self._add() |
| 115 | + else: |
| 116 | + super().keyPressEvent(e) |
| 117 | + |
| 118 | + def selected_rows(self, view): |
| 119 | + """ Return the selected rows in the view. |
| 120 | + """ |
| 121 | + rows = view.selectionModel().selectedRows() |
| 122 | + model = view.model() |
| 123 | + return [model.mapToSource(r) for r in rows] |
| 124 | + |
| 125 | + def add_variables(self): |
| 126 | + view = self.view_other |
| 127 | + model = self.master.model_other |
| 128 | + |
| 129 | + indices = self.selected_rows(view) |
| 130 | + variables = [model.data(ind, Qt.EditRole) for ind in indices] |
| 131 | + |
| 132 | + for i in sorted((ind.row() for ind in indices), reverse=True): |
| 133 | + del model[i] |
| 134 | + |
| 135 | + self.master.model_selected.extend(variables) |
| 136 | + |
| 137 | + |
| 138 | +class VariablesSelection: |
| 139 | + def __call__(self, master, model_selected, model_other): |
| 140 | + self.master = master |
| 141 | + |
| 142 | + params_view = {"sizePolicy": QSizePolicy(*SIZE_POLICY_ADAPTING), |
| 143 | + "selectionMode": QListView.ExtendedSelection, |
| 144 | + "dragEnabled": True, |
| 145 | + "defaultDropAction": Qt.MoveAction, |
| 146 | + "dragDropOverwriteMode": False, |
| 147 | + "dragDropMode": QListView.DragDrop} |
| 148 | + |
| 149 | + self.view_selected = view = gui.listView(widget=master.controlArea, master=master, |
| 150 | + box="Displayed Axes", **params_view) |
| 151 | + view.box.setMinimumHeight(120) |
| 152 | + view.viewport().setAcceptDrops(True) |
| 153 | + |
| 154 | + delete = QAction( |
| 155 | + "Delete", view, |
| 156 | + shortcut=QKeySequence(Qt.Key_Delete), |
| 157 | + triggered=self.__deactivate_selection |
| 158 | + ) |
| 159 | + view.addAction(delete) |
| 160 | + |
| 161 | + self.model_selected = model = model_selected |
| 162 | + |
| 163 | + model.rowsInserted.connect(master.invalidate_plot) |
| 164 | + model.rowsRemoved.connect(master.invalidate_plot) |
| 165 | + model.rowsMoved.connect(master.invalidate_plot) |
| 166 | + |
| 167 | + view.setModel(model) |
| 168 | + |
| 169 | + addClassLabel = QAction("+", master, |
| 170 | + toolTip="Add new class label", |
| 171 | + triggered=self._action_add) |
| 172 | + removeClassLabel = QAction(unicodedata.lookup("MINUS SIGN"), master, |
| 173 | + toolTip="Remove selected class label", |
| 174 | + triggered=self.__deactivate_selection) |
| 175 | + |
| 176 | + add_remove = itemmodels.ModelActionsWidget([addClassLabel, removeClassLabel], master) |
| 177 | + add_remove.layout().addStretch(10) |
| 178 | + add_remove.layout().setSpacing(1) |
| 179 | + add_remove.setSizePolicy(*SIZE_POLICY_FIXED) |
| 180 | + view.box.layout().addWidget(add_remove) |
| 181 | + |
| 182 | + self.add_remove = add_remove |
| 183 | + self.box = add_remove.buttons[1] |
| 184 | + |
| 185 | + self.model_other = model_other |
| 186 | + |
| 187 | + def set_enabled(self, is_enabled): |
| 188 | + self.view_selected.setEnabled(is_enabled) |
| 189 | + for btn in self.add_remove.buttons: |
| 190 | + btn.setEnabled(is_enabled) |
| 191 | + |
| 192 | + def display_all(self): |
| 193 | + self.model_selected[:] += self.model_other[:] |
| 194 | + self.model_other[:] = [] |
| 195 | + |
| 196 | + def display_none(self): |
| 197 | + self.model_other[:] += self.model_selected[:] |
| 198 | + self.model_selected[:] = [] |
| 199 | + |
| 200 | + def __deactivate_selection(self): |
| 201 | + view = self.view_selected |
| 202 | + model = self.model_selected |
| 203 | + indices = view.selectionModel().selectedRows() |
| 204 | + |
| 205 | + variables = [model.data(ind, Qt.EditRole) for ind in indices] |
| 206 | + |
| 207 | + for i in sorted((ind.row() for ind in indices), reverse=True): |
| 208 | + del model[i] |
| 209 | + |
| 210 | + self.model_other.extend(variables) |
| 211 | + |
| 212 | + def _action_add(self): |
| 213 | + self.add_variables_dialog = AddVariablesDialog(self, self.model_other) |
| 214 | + |
| 215 | + @staticmethod |
| 216 | + def encode_var_state(lists): |
| 217 | + return {(type(var), var.name): (source_ind, pos) |
| 218 | + for source_ind, var_list in enumerate(lists) |
| 219 | + for pos, var in enumerate(var_list) |
| 220 | + if isinstance(var, Variable)} |
| 221 | + |
| 222 | + @staticmethod |
| 223 | + def decode_var_state(state, lists): |
| 224 | + all_vars = reduce(list.__iadd__, lists, []) |
| 225 | + |
| 226 | + newlists = [[] for _ in lists] |
| 227 | + for var in all_vars: |
| 228 | + source, pos = state[(type(var), var.name)] |
| 229 | + newlists[source].append((pos, var)) |
| 230 | + return [[var for _, var in sorted(newlist, key=itemgetter(0))] |
| 231 | + for newlist in newlists] |
| 232 | + |
| 233 | + |
42 | 234 | class OrientedWidget(QWidget): |
43 | 235 | ''' |
44 | 236 | A simple QWidget with a box layout that matches its ``orientation``. |
|
0 commit comments