class OWImageViewer(widget.OWWidget): name = "Image Viewer" description = "View images referred to in the data." icon = "icons/ImageViewer.svg" priority = 4050 inputs = [("Data", Orange.data.Table, "setData")] outputs = [( "Data", Orange.data.Table, )] settingsHandler = settings.DomainContextHandler() imageAttr = settings.ContextSetting(0) titleAttr = settings.ContextSetting(0) zoom = settings.Setting(25) autoCommit = settings.Setting(False) show_save_graph = True want_graph = True def __init__(self, parent=None): super().__init__(parent) self.info = gui.widgetLabel(gui.widgetBox(self.controlArea, "Info"), "Waiting for input\n") self.imageAttrCB = gui.comboBox( self.controlArea, self, "imageAttr", box="Image Filename Attribute", tooltip="Attribute with image filenames", callback=[self.clearScene, self.setupScene], addSpace=True) self.titleAttrCB = gui.comboBox(self.controlArea, self, "titleAttr", box="Title Attribute", tooltip="Attribute with image title", callback=self.updateTitles, addSpace=True) gui.hSlider(self.controlArea, self, "zoom", box="Zoom", minValue=1, maxValue=100, step=1, callback=self.updateZoom, createLabel=False) gui.separator(self.controlArea) gui.auto_commit(self.controlArea, self, "autoCommit", "Commit", "Auto commit") gui.rubber(self.controlArea) self.scene = GraphicsScene() self.sceneView = QGraphicsView(self.scene, self) self.sceneView.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.sceneView.setRenderHint(QPainter.Antialiasing, True) self.sceneView.setRenderHint(QPainter.TextAntialiasing, True) self.sceneView.setFocusPolicy(Qt.WheelFocus) self.sceneView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.sceneView.installEventFilter(self) self.mainArea.layout().addWidget(self.sceneView) self.scene.selectionChanged.connect(self.onSelectionChanged) self.scene.selectionRectPointChanged.connect( self.onSelectionRectPointChanged, Qt.QueuedConnection) self.graphButton.clicked.connect(self.saveScene) self.resize(800, 600) self.thumbnailWidget = None self.sceneLayout = None self.selectedIndices = [] #: List of _ImageItems self.items = [] self._errcount = 0 self._successcount = 0 self.loader = ImageLoader(self) def setData(self, data): self.closeContext() self.clear() self.data = data if data is not None: domain = data.domain self.allAttrs = domain.variables + domain.metas self.stringAttrs = [a for a in self.allAttrs if a.is_string] self.stringAttrs = sorted(self.stringAttrs, key=lambda attr: 0 if "type" in attr.attributes else 1) indices = [ i for i, var in enumerate(self.stringAttrs) if var.attributes.get("type") == "image" ] if indices: self.imageAttr = indices[0] self.imageAttrCB.setModel(VariableListModel(self.stringAttrs)) self.titleAttrCB.setModel(VariableListModel(self.allAttrs)) self.openContext(data) self.imageAttr = max( min(self.imageAttr, len(self.stringAttrs) - 1), 0) self.titleAttr = max(min(self.titleAttr, len(self.allAttrs) - 1), 0) if self.stringAttrs: self.setupScene() else: self.info.setText("Waiting for input\n") def clear(self): self.data = None self.information(0) self.error(0) self.imageAttrCB.clear() self.titleAttrCB.clear() self.clearScene() def setupScene(self): self.information(0) self.error(0) if self.data: attr = self.stringAttrs[self.imageAttr] titleAttr = self.allAttrs[self.titleAttr] instances = [ inst for inst in self.data if numpy.isfinite(inst[attr]) ] widget = ThumbnailWidget() layout = widget.layout() self.scene.addItem(widget) for i, inst in enumerate(instances): url = self.urlFromValue(inst[attr]) title = str(inst[titleAttr]) thumbnail = GraphicsThumbnailWidget(QPixmap(), title=title, parent=widget) thumbnail.setToolTip(url.toString()) thumbnail.instance = inst layout.addItem(thumbnail, i / 5, i % 5) if url.isValid(): future = self.loader.get(url) watcher = _FutureWatcher(parent=thumbnail) # watcher = FutureWatcher(future, parent=thumbnail) def set_pixmap(thumb=thumbnail, future=future): if future.cancelled(): return if future.exception(): # Should be some generic error image. pixmap = QPixmap() thumb.setToolTip(thumb.toolTip() + "\n" + str(future.exception())) else: pixmap = QPixmap.fromImage(future.result()) thumb.setPixmap(pixmap) if not pixmap.isNull(): thumb.setThumbnailSize(self.pixmapSize(pixmap)) self._updateStatus(future) watcher.finished.connect(set_pixmap, Qt.QueuedConnection) watcher.setFuture(future) else: future = None self.items.append(_ImageItem(i, thumbnail, url, future)) widget.show() widget.geometryChanged.connect(self._updateSceneRect) self.info.setText("Retrieving...\n") self.thumbnailWidget = widget self.sceneLayout = layout if self.sceneLayout: width = (self.sceneView.width() - self.sceneView.verticalScrollBar().width()) self.thumbnailWidget.reflow(width) self.thumbnailWidget.setPreferredWidth(width) self.sceneLayout.activate() def urlFromValue(self, value): variable = value.variable origin = variable.attributes.get("origin", "") if origin and QDir(origin).exists(): origin = QUrl.fromLocalFile(origin) elif origin: origin = QUrl(origin) if not origin.scheme(): origin.setScheme("file") else: origin = QUrl("") base = origin.path() if base.strip() and not base.endswith("/"): origin.setPath(base + "/") name = QUrl(str(value)) url = origin.resolved(name) if not url.scheme(): url.setScheme("file") return url def pixmapSize(self, pixmap): """ Return the preferred pixmap size based on the current `zoom` value. """ scale = 2 * self.zoom / 100.0 size = QSizeF(pixmap.size()) * scale return size.expandedTo(QSizeF(16, 16)) def clearScene(self): for item in self.items: if item.future: item.future._reply.close() item.future.cancel() self.items = [] self._errcount = 0 self._successcount = 0 self.scene.clear() self.thumbnailWidget = None self.sceneLayout = None def thumbnailItems(self): return [item.widget for item in self.items] def updateZoom(self): for item in self.thumbnailItems(): item.setThumbnailSize(self.pixmapSize(item.pixmap())) if self.thumbnailWidget: width = (self.sceneView.width() - self.sceneView.verticalScrollBar().width()) self.thumbnailWidget.reflow(width) self.thumbnailWidget.setPreferredWidth(width) if self.sceneLayout: self.sceneLayout.activate() def updateTitles(self): titleAttr = self.allAttrs[self.titleAttr] for item in self.items: item.widget.setTitle(str(item.widget.instance[titleAttr])) def onSelectionChanged(self): selected = [item for item in self.items if item.widget.isSelected()] self.selectedIndices = [item.index for item in selected] self.commit() def onSelectionRectPointChanged(self, point): self.sceneView.ensureVisible(QRectF(point, QSizeF(1, 1)), 5, 5) def commit(self): if self.data: if self.selectedIndices: selected = self.data[self.selectedIndices] else: selected = None self.send("Data", selected) else: self.send("Data", None) def saveScene(self): from OWDlgs import OWChooseImageSizeDlg sizeDlg = OWChooseImageSizeDlg(self.scene, parent=self) sizeDlg.exec_() def _updateStatus(self, future): if future.cancelled(): return if future.exception(): self._errcount += 1 _log.debug("Error: %r", future.exception()) else: self._successcount += 1 count = len([item for item in self.items if item.future is not None]) self.info.setText("Retrieving:\n" + "{} of {} images".format(self._successcount, count)) if self._errcount + self._successcount == count: if self._errcount: self.info.setText( "Done:\n" + "{} images, {} errors".format(count, self._errcount)) else: self.info.setText("Done:\n" + "{} images".format(count)) attr = self.stringAttrs[self.imageAttr] if self._errcount == count and not "type" in attr.attributes: self.error( 0, "No images found! Make sure the '%s' attribute " "is tagged with 'type=image'" % attr.name) def _updateSceneRect(self): self.scene.setSceneRect(self.scene.itemsBoundingRect()) def onDeleteWidget(self): for item in self.items: item.future._reply.abort() item.future.cancel() def eventFilter(self, receiver, event): if receiver is self.sceneView and event.type() == QEvent.Resize \ and self.thumbnailWidget: width = (self.sceneView.width() - self.sceneView.verticalScrollBar().width()) self.thumbnailWidget.reflow(width) self.thumbnailWidget.setPreferredWidth(width) return super(OWImageViewer, self).eventFilter(receiver, event)
class OWImageViewer(widget.OWWidget): name = "Image Viewer" description = "View images referred to in the data." icon = "icons/ImageViewer.svg" priority = 4050 inputs = [("Data", Orange.data.Table, "setData")] outputs = [("Data", Orange.data.Table, )] settingsHandler = settings.DomainContextHandler() imageAttr = settings.ContextSetting(0) titleAttr = settings.ContextSetting(0) zoom = settings.Setting(25) autoCommit = settings.Setting(False) buttons_area_orientation = Qt.Vertical graph_name = "scene" def __init__(self): super().__init__() self.info = gui.widgetLabel( gui.vBox(self.controlArea, "Info"), "Waiting for input.\n" ) self.imageAttrCB = gui.comboBox( self.controlArea, self, "imageAttr", box="Image Filename Attribute", tooltip="Attribute with image filenames", callback=[self.clearScene, self.setupScene], contentsLength=12, addSpace=True, ) self.titleAttrCB = gui.comboBox( self.controlArea, self, "titleAttr", box="Title Attribute", tooltip="Attribute with image title", callback=self.updateTitles, contentsLength=12, addSpace=True ) gui.hSlider( self.controlArea, self, "zoom", box="Zoom", minValue=1, maxValue=100, step=1, callback=self.updateZoom, createLabel=False ) gui.rubber(self.controlArea) gui.auto_commit(self.buttonsArea, self, "autoCommit", "Send", box=False) self.scene = GraphicsScene() self.sceneView = QGraphicsView(self.scene, self) self.sceneView.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.sceneView.setRenderHint(QPainter.Antialiasing, True) self.sceneView.setRenderHint(QPainter.TextAntialiasing, True) self.sceneView.setFocusPolicy(Qt.WheelFocus) self.sceneView.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.sceneView.installEventFilter(self) self.mainArea.layout().addWidget(self.sceneView) self.scene.selectionChanged.connect(self.onSelectionChanged) self.scene.selectionRectPointChanged.connect( self.onSelectionRectPointChanged, Qt.QueuedConnection ) self.resize(800, 600) self.thumbnailWidget = None self.sceneLayout = None self.selectedIndices = [] #: List of _ImageItems self.items = [] self._errcount = 0 self._successcount = 0 self.loader = ImageLoader(self) def setData(self, data): self.closeContext() self.clear() self.data = data if data is not None: domain = data.domain self.allAttrs = domain.variables + domain.metas self.stringAttrs = [a for a in self.allAttrs if a.is_string] self.stringAttrs = sorted( self.stringAttrs, key=lambda attr: 0 if "type" in attr.attributes else 1 ) indices = [i for i, var in enumerate(self.stringAttrs) if var.attributes.get("type") == "image"] if indices: self.imageAttr = indices[0] self.imageAttrCB.setModel(VariableListModel(self.stringAttrs)) self.titleAttrCB.setModel(VariableListModel(self.allAttrs)) self.openContext(data) self.imageAttr = max(min(self.imageAttr, len(self.stringAttrs) - 1), 0) self.titleAttr = max(min(self.titleAttr, len(self.allAttrs) - 1), 0) if self.stringAttrs: self.setupScene() else: self.info.setText("Waiting for input.\n") def clear(self): self.data = None self.information(0) self.error(0) self.imageAttrCB.clear() self.titleAttrCB.clear() self.clearScene() def setupScene(self): self.information(0) self.error(0) if self.data: attr = self.stringAttrs[self.imageAttr] titleAttr = self.allAttrs[self.titleAttr] instances = [inst for inst in self.data if numpy.isfinite(inst[attr])] widget = ThumbnailWidget() layout = widget.layout() self.scene.addItem(widget) for i, inst in enumerate(instances): url = self.urlFromValue(inst[attr]) title = str(inst[titleAttr]) thumbnail = GraphicsThumbnailWidget( QPixmap(), title=title, parent=widget ) thumbnail.setToolTip(url.toString()) thumbnail.instance = inst layout.addItem(thumbnail, i / 5, i % 5) if url.isValid(): future = self.loader.get(url) watcher = _FutureWatcher(parent=thumbnail) # watcher = FutureWatcher(future, parent=thumbnail) def set_pixmap(thumb=thumbnail, future=future): if future.cancelled(): return if future.exception(): # Should be some generic error image. pixmap = QPixmap() thumb.setToolTip(thumb.toolTip() + "\n" + str(future.exception())) else: pixmap = QPixmap.fromImage(future.result()) thumb.setPixmap(pixmap) if not pixmap.isNull(): thumb.setThumbnailSize(self.pixmapSize(pixmap)) self._updateStatus(future) watcher.finished.connect(set_pixmap, Qt.QueuedConnection) watcher.setFuture(future) else: future = None self.items.append(_ImageItem(i, thumbnail, url, future)) widget.show() widget.geometryChanged.connect(self._updateSceneRect) self.info.setText("Retrieving...\n") self.thumbnailWidget = widget self.sceneLayout = layout if self.sceneLayout: width = (self.sceneView.width() - self.sceneView.verticalScrollBar().width()) self.thumbnailWidget.reflow(width) self.thumbnailWidget.setPreferredWidth(width) self.sceneLayout.activate() def urlFromValue(self, value): variable = value.variable origin = variable.attributes.get("origin", "") if origin and QDir(origin).exists(): origin = QUrl.fromLocalFile(origin) elif origin: origin = QUrl(origin) if not origin.scheme(): origin.setScheme("file") else: origin = QUrl("") base = origin.path() if base.strip() and not base.endswith("/"): origin.setPath(base + "/") name = QUrl(str(value)) url = origin.resolved(name) if not url.scheme(): url.setScheme("file") return url def pixmapSize(self, pixmap): """ Return the preferred pixmap size based on the current `zoom` value. """ scale = 2 * self.zoom / 100.0 size = QSizeF(pixmap.size()) * scale return size.expandedTo(QSizeF(16, 16)) def clearScene(self): for item in self.items: if item.future: item.future._reply.close() item.future.cancel() self.items = [] self._errcount = 0 self._successcount = 0 self.scene.clear() self.thumbnailWidget = None self.sceneLayout = None def thumbnailItems(self): return [item.widget for item in self.items] def updateZoom(self): for item in self.thumbnailItems(): item.setThumbnailSize(self.pixmapSize(item.pixmap())) if self.thumbnailWidget: width = (self.sceneView.width() - self.sceneView.verticalScrollBar().width()) self.thumbnailWidget.reflow(width) self.thumbnailWidget.setPreferredWidth(width) if self.sceneLayout: self.sceneLayout.activate() def updateTitles(self): titleAttr = self.allAttrs[self.titleAttr] for item in self.items: item.widget.setTitle(str(item.widget.instance[titleAttr])) def onSelectionChanged(self): selected = [item for item in self.items if item.widget.isSelected()] self.selectedIndices = [item.index for item in selected] self.commit() def onSelectionRectPointChanged(self, point): self.sceneView.ensureVisible(QRectF(point, QSizeF(1, 1)), 5, 5) def commit(self): if self.data: if self.selectedIndices: selected = self.data[self.selectedIndices] else: selected = None self.send("Data", selected) else: self.send("Data", None) def _updateStatus(self, future): if future.cancelled(): return if future.exception(): self._errcount += 1 _log.debug("Error: %r", future.exception()) else: self._successcount += 1 count = len([item for item in self.items if item.future is not None]) self.info.setText( "Retrieving:\n" + "{} of {} images".format(self._successcount, count)) if self._errcount + self._successcount == count: if self._errcount: self.info.setText( "Done:\n" + "{} images, {} errors".format(count, self._errcount) ) else: self.info.setText( "Done:\n" + "{} images".format(count) ) attr = self.stringAttrs[self.imageAttr] if self._errcount == count and not "type" in attr.attributes: self.error(0, "No images found! Make sure the '%s' attribute " "is tagged with 'type=image'" % attr.name) def _updateSceneRect(self): self.scene.setSceneRect(self.scene.itemsBoundingRect()) def onDeleteWidget(self): for item in self.items: item.future._reply.abort() item.future.cancel() def eventFilter(self, receiver, event): if receiver is self.sceneView and event.type() == QEvent.Resize \ and self.thumbnailWidget: width = (self.sceneView.width() - self.sceneView.verticalScrollBar().width()) self.thumbnailWidget.reflow(width) self.thumbnailWidget.setPreferredWidth(width) return super(OWImageViewer, self).eventFilter(receiver, event)
class OWQualityControl(widget.OWWidget): name = "Quality Control" description = "Experiment quality control" icon = "../widgets/icons/QualityControl.svg" priority = 5000 inputs = [("Experiment Data", Orange.data.Table, "set_data")] outputs = [] DISTANCE_FUNCTIONS = [("Distance from Pearson correlation", dist_pcorr), ("Euclidean distance", dist_eucl), ("Distance from Spearman correlation", dist_spearman)] settingsHandler = SetContextHandler() split_by_labels = settings.ContextSetting({}) sort_by_labels = settings.ContextSetting({}) selected_distance_index = settings.Setting(0) def __init__(self, parent=None): super().__init__(parent) ## Attributes self.data = None self.distances = None self.groups = None self.unique_pos = None self.base_group_index = 0 ## GUI box = gui.widgetBox(self.controlArea, "Info") self.info_box = gui.widgetLabel(box, "\n") ## Separate By box box = gui.widgetBox(self.controlArea, "Separate By") self.split_by_model = itemmodels.PyListModel(parent=self) self.split_by_view = QListView() self.split_by_view.setSelectionMode(QListView.ExtendedSelection) self.split_by_view.setModel(self.split_by_model) box.layout().addWidget(self.split_by_view) self.split_by_view.selectionModel().selectionChanged.connect( self.on_split_key_changed) ## Sort By box box = gui.widgetBox(self.controlArea, "Sort By") self.sort_by_model = itemmodels.PyListModel(parent=self) self.sort_by_view = QListView() self.sort_by_view.setSelectionMode(QListView.ExtendedSelection) self.sort_by_view.setModel(self.sort_by_model) box.layout().addWidget(self.sort_by_view) self.sort_by_view.selectionModel().selectionChanged.connect( self.on_sort_key_changed) ## Distance box box = gui.widgetBox(self.controlArea, "Distance Measure") gui.comboBox(box, self, "selected_distance_index", items=[name for name, _ in self.DISTANCE_FUNCTIONS], callback=self.on_distance_measure_changed) self.scene = QGraphicsScene() self.scene_view = QGraphicsView(self.scene) self.scene_view.setRenderHints(QPainter.Antialiasing) self.scene_view.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.mainArea.layout().addWidget(self.scene_view) self.scene_view.installEventFilter(self) self._disable_updates = False self._cached_distances = {} self._base_index_hints = {} self.main_widget = None self.resize(800, 600) def clear(self): """Clear the widget state.""" self.data = None self.distances = None self.groups = None self.unique_pos = None with disable_updates(self): self.split_by_model[:] = [] self.sort_by_model[:] = [] self.main_widget = None self.scene.clear() self.info_box.setText("\n") self._cached_distances = {} def set_data(self, data=None): """Set input experiment data.""" self.closeContext() self.clear() self.error(0) self.warning(0) if data is not None: keys = self.get_suitable_keys(data) if not keys: self.error(0, "Data has no suitable feature labels.") data = None self.data = data if data is not None: self.on_new_data() def update_label_candidates(self): """Update the label candidates selection GUI (Group/Sort By views). """ keys = self.get_suitable_keys(self.data) with disable_updates(self): self.split_by_model[:] = keys self.sort_by_model[:] = keys def get_suitable_keys(self, data): """ Return suitable attr label keys from the data where the key has at least two unique values in the data. """ attrs = [attr.attributes.items() for attr in data.domain.attributes] attrs = reduce(operator.iadd, attrs, []) # in case someone put non string values in attributes dict attrs = [(str(key), str(value)) for key, value in attrs] attrs = set(attrs) values = defaultdict(set) for key, value in attrs: values[key].add(value) keys = [key for key in values if len(values[key]) > 1] return keys def selected_split_by_labels(self): """Return the current selected split labels. """ sel_m = self.split_by_view.selectionModel() indices = [r.row() for r in sel_m.selectedRows()] return [self.sort_by_model[i] for i in indices] def selected_sort_by_labels(self): """Return the current selected sort labels """ sel_m = self.sort_by_view.selectionModel() indices = [r.row() for r in sel_m.selectedRows()] return [self.sort_by_model[i] for i in indices] def selected_distance(self): """Return the selected distance function. """ return self.DISTANCE_FUNCTIONS[self.selected_distance_index][1] def selected_base_group_index(self): """Return the selected base group index """ return self.base_group_index def selected_base_indices(self, base_group_index=None): indices = [] for g, ind in self.groups: if base_group_index is None: label = group_label(self.selected_split_by_labels(), g) ind = [i for i in ind if i is not None] i = self._base_index_hints.get(label, ind[0] if ind else None) else: i = ind[base_group_index] indices.append(i) return indices def on_new_data(self): """We have new data and need to recompute all. """ self.closeContext() self.update_label_candidates() self.info_box.setText( "%s genes \n%s experiments" % (len(self.data), len(self.data.domain.attributes)) ) self.base_group_index = 0 keys = self.get_suitable_keys(self.data) self.openContext(keys) ## Restore saved context settings (split/sort selection) split_by_labels = self.split_by_labels sort_by_labels = self.sort_by_labels def select(model, selection_model, selected_items): """Select items in a Qt item model view """ all_items = list(model) try: indices = [all_items.index(item) for item in selected_items] except: indices = [] for ind in indices: selection_model.select(model.index(ind), QItemSelectionModel.Select) with disable_updates(self): select(self.split_by_view.model(), self.split_by_view.selectionModel(), split_by_labels) select(self.sort_by_view.model(), self.sort_by_view.selectionModel(), sort_by_labels) with widget_disable(self): self.split_and_update() def on_split_key_changed(self, *args): """Split key has changed """ with widget_disable(self): if not self._disable_updates: self.base_group_index = 0 self.split_by_labels = self.selected_split_by_labels() self.split_and_update() def on_sort_key_changed(self, *args): """Sort key has changed """ with widget_disable(self): if not self._disable_updates: self.base_group_index = 0 self.sort_by_labels = self.selected_sort_by_labels() self.split_and_update() def on_distance_measure_changed(self): """Distance measure has changed """ if self.data is not None: with widget_disable(self): self.update_distances() self.replot_experiments() def on_view_resize(self, size): """The view with the quality plot has changed """ if self.main_widget: current = self.main_widget.size() self.main_widget.resize(size.width() - 6, current.height()) self.scene.setSceneRect(self.scene.itemsBoundingRect()) def on_rug_item_clicked(self, item): """An ``item`` in the quality plot has been clicked. """ update = False sort_by_labels = self.selected_sort_by_labels() if sort_by_labels and item.in_group: ## The item is part of the group if item.group_index != self.base_group_index: self.base_group_index = item.group_index update = True else: if sort_by_labels: # If the user clicked on an background item it # invalidates the sorted labels selection with disable_updates(self): self.sort_by_view.selectionModel().clear() update = True index = item.index group = item.group label = group_label(self.selected_split_by_labels(), group) if self._base_index_hints.get(label, 0) != index: self._base_index_hints[label] = index update = True if update: with widget_disable(self): self.split_and_update() def eventFilter(self, obj, event): if obj is self.scene_view and event.type() == QEvent.Resize: self.on_view_resize(event.size()) return super().eventFilter(obj, event) def split_and_update(self): """ Split the data based on the selected sort/split labels and update the quality plot. """ split_labels = self.selected_split_by_labels() sort_labels = self.selected_sort_by_labels() self.warning(0) if not split_labels: self.warning(0, "No separate by label selected.") self.groups, self.unique_pos = \ exp.separate_by(self.data, split_labels, consider=sort_labels, add_empty=True) self.groups = sorted(self.groups.items(), key=lambda t: list(map(float_if_posible, t[0]))) self.unique_pos = sorted(self.unique_pos.items(), key=lambda t: list(map(float_if_posible, t[0]))) if self.groups: if sort_labels: group_base = self.selected_base_group_index() base_indices = self.selected_base_indices(group_base) else: base_indices = self.selected_base_indices() self.update_distances(base_indices) self.replot_experiments() def get_cached_distances(self, measure): if measure not in self._cached_distances: attrs = self.data.domain.attributes mat = numpy.zeros((len(attrs), len(attrs))) self._cached_distances[measure] = \ (mat, set(zip(range(len(attrs)), range(len(attrs))))) return self._cached_distances[measure] def get_cached_distance(self, measure, i, j): matrix, computed = self.get_cached_distances(measure) key = (i, j) if i < j else (j, i) if key in computed: return matrix[i, j] else: return None def get_distance(self, measure, i, j): d = self.get_cached_distance(measure, i, j) if d is None: vec_i = take_columns(self.data, [i]) vec_j = take_columns(self.data, [j]) d = measure(vec_i, vec_j) mat, computed = self.get_cached_distances(measure) mat[i, j] = d key = key = (i, j) if i < j else (j, i) computed.add(key) return d def store_distance(self, measure, i, j, dist): matrix, computed = self.get_cached_distances(measure) key = (i, j) if i < j else (j, i) matrix[j, i] = matrix[i, j] = dist computed.add(key) def update_distances(self, base_indices=()): """Recompute the experiment distances. """ distance = self.selected_distance() if base_indices == (): base_group_index = self.selected_base_group_index() base_indices = [ind[base_group_index] \ for _, ind in self.groups] assert(len(base_indices) == len(self.groups)) base_distances = [] attributes = self.data.domain.attributes pb = gui.ProgressBar(self, len(self.groups) * len(attributes)) for (group, indices), base_index in zip(self.groups, base_indices): # Base column of the group if base_index is not None: base_vec = take_columns(self.data, [base_index]) distances = [] # Compute the distances between base column # and all the rest data columns. for i in range(len(attributes)): if i == base_index: distances.append(0.0) elif self.get_cached_distance(distance, i, base_index) is not None: distances.append(self.get_cached_distance(distance, i, base_index)) else: vec_i = take_columns(self.data, [i]) dist = distance(base_vec, vec_i) self.store_distance(distance, i, base_index, dist) distances.append(dist) pb.advance() base_distances.append(distances) else: base_distances.append(None) pb.finish() self.distances = base_distances def replot_experiments(self): """Replot the whole quality plot. """ self.scene.clear() labels = [] max_dist = numpy.nanmax(list(filter(None, self.distances))) rug_widgets = [] group_pen = QPen(Qt.black) group_pen.setWidth(2) group_pen.setCapStyle(Qt.RoundCap) background_pen = QPen(QColor(0, 0, 250, 150)) background_pen.setWidth(1) background_pen.setCapStyle(Qt.RoundCap) main_widget = QGraphicsWidget() layout = QGraphicsGridLayout() attributes = self.data.domain.attributes if self.data is not None: for (group, indices), dist_vec in zip(self.groups, self.distances): indices_set = set(indices) rug_items = [] if dist_vec is not None: for i, attr in enumerate(attributes): # Is this a within group distance or background in_group = i in indices_set if in_group: rug_item = ClickableRugItem(dist_vec[i] / max_dist, 1.0, self.on_rug_item_clicked) rug_item.setPen(group_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group_index = indices.index(i) rug_item.setZValue(rug_item.zValue() + 1) else: rug_item = ClickableRugItem(dist_vec[i] / max_dist, 0.85, self.on_rug_item_clicked) rug_item.setPen(background_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group = group rug_item.index = i rug_item.in_group = in_group rug_items.append(rug_item) rug_widget = RugGraphicsWidget(parent=main_widget) rug_widget.set_rug(rug_items) rug_widgets.append(rug_widget) label = group_label(self.selected_split_by_labels(), group) label_item = QGraphicsSimpleTextItem(label, main_widget) label_item = GraphicsSimpleTextLayoutItem(label_item, parent=layout) label_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) labels.append(label_item) for i, (label, rug_w) in enumerate(zip(labels, rug_widgets)): layout.addItem(label, i, 0, Qt.AlignVCenter) layout.addItem(rug_w, i, 1) layout.setRowMaximumHeight(i, 30) main_widget.setLayout(layout) self.scene.addItem(main_widget) self.main_widget = main_widget self.rug_widgets = rug_widgets self.labels = labels self.on_view_resize(self.scene_view.size())
class PlayMapWidget(QWidget): """ Widget for displaying playing world .. versionadded:: 0.5 """ def __init__(self, parent, model, surface_manager, action_factory, rng, rules_engine, configuration): """ Default constructor """ super().__init__(parent) self.model = model self.scene = None self.surface_manager = surface_manager self.action_factory = action_factory self.rng = rng self.rules_engine = rules_engine self.configuration = configuration self.current_level = None self.view = None self.animation_adapters = [] self.animation_timers = [] for adapter in range(10): self.animation_adapters.append(TimerAdapter()) self.animation_timers.append(QTimer(self)) self.animation_timers[adapter].timeout.connect(self.animation_adapters[adapter].trigger_animations) self.animation_timers[adapter].start(450 + adapter * 10) self.animations = [] self.move_controller = MoveController(action_factory = action_factory, rng = rng) self.__set_layout() self.keymap, self.move_key_map = self._construct_keymaps( configuration.controls) self.animation_factory = AnimationFactory() MenuRequested = pyqtSignal(name='MenuRequested') EndScreenRequested = pyqtSignal(name='EndScreenRequested') NextSpellRequested = pyqtSignal(name='NextSpellRequested') PreviousSpellRequested = pyqtSignal(name='PreviousSpellRequested') def _construct_keymaps(self, config): """ Construct keymaps for handling input """ keymap = {} move_keymap = {} for key in config.move_left: keymap[key] = self._move move_keymap[key] = 7 for key in config.move_up: keymap[key] = self._move move_keymap[key] = 1 for key in config.move_right: keymap[key] = self._move move_keymap[key] = 3 for key in config.move_down: keymap[key] = self._move move_keymap[key] = 5 for key in config.start: keymap[key] = self._menu for key in config.action_a: keymap[key] = self._action_a for key in config.back: keymap[key] = self._back for key in config.left_shoulder: keymap[key] = self._shoulder_left for key in config.right_shoulder: keymap[key] = self._shoulder_right for key in config.mode_1: keymap[key] = self._zoom_out for key in config.mode_2: keymap[key] = self._zoom_in return keymap, move_keymap def __set_layout(self): """ Set layout of this widget """ self.scene = QGraphicsScene() layout = QHBoxLayout() self.view = QGraphicsView(self.scene) self.view.setFocusPolicy(Qt.StrongFocus) self.view.installEventFilter(self) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) layout.addWidget(self.view) self.setLayout(layout) def construct_scene(self): """ Construct scene to display """ self.__construct_scene(self.model, self.scene) self.model.player.register_for_updates(self) self.model.register_event_listener(self) self.__center_view_on_character(self.model.player) def __center_view_on_character(self, entity): """ Center view on given entity """ location = entity.location width = 32 height = 32 self.view.setSceneRect((location[0] * 32) - width // 2, (location[1] * 32) - height // 2, width, height) def __construct_scene(self, model, scene): """ Constructs scene to display """ for anim in [x for x in self.animations]: anim.stop() anim.clear() for adapter in self.animation_adapters: adapter.glyphs.clear() self.animations = [] scene.clear() self.current_level = model.player.level for location, tile in get_tiles(self.current_level): if tile['\ufdd0:floor']: new_glyph = MapGlyph(self.surface_manager.get_icon(tile['\ufdd0:floor']), None, self.animation_adapters[0]) new_glyph.setZValue(zorder_floor) new_glyph.setPos(location[0] * 32, location[1] * 32) scene.addItem(new_glyph) if tile['\ufdd0:wall']: new_glyph = MapGlyph(self.surface_manager.get_icon(tile['\ufdd0:wall']), None) new_glyph.setZValue(zorder_wall) new_glyph.setPos(location[0] * 32, location[1] * 32) scene.addItem(new_glyph) for tile_id in tile['\ufdd0:ornamentation']: new_glyph = MapGlyph(self.surface_manager.get_icon(tile_id), None, self.rng.choice(self.animation_adapters)) new_glyph.setZValue(zorder_ornament) new_glyph.setPos(location[0] * 32, location[1] * 32) scene.addItem(new_glyph) for item in tile['\ufdd0:items']: self.add_glyph(item, scene, zorder_item) for trap in tile['\ufdd0:traps']: self.add_glyph(trap, scene, zorder_trap) for creature in get_characters(self.current_level): self.add_glyph(creature, scene, zorder_character) def add_glyph(self, entity, scene, z_order): """ Add graphical representation of an entity :param entity: entity to display :param scene: scene where glyph will be added :type scene: QGraphicsScene :param z_order: z-order of entity being displayed :type z_order: int """ new_glyph = MapGlyph(self.surface_manager.get_icon(entity.icon), entity, self.rng.choice(self.animation_adapters)) new_glyph.setZValue(z_order) new_glyph.setPos(entity.location[0] * 32, entity.location[1] * 32) scene.addItem(new_glyph) def remove_glyph(self, entity): """ Remove graphical representation of an entity """ glyphs = [x for x in self.view.items() if (hasattr(x, 'entity')) and (x.entity == entity)] for glyph in glyphs: self.view.scene().removeItem(glyph) def receive_event(self, event): """ Receive event from model """ anim = self.animation_factory.create_animation(event) anim.trigger(self) def remove_finished_animation(self): """ Remove finished animation """ finished_animations = [x for x in self.animations if x.state() == QAbstractAnimation.Stopped] counters = [x.animationAt(0).targetObject().object_to_animate for x in finished_animations] #TODO: works only if single thing is animated for item in finished_animations: item.clear() self.animations.remove(item) def receive_update(self, event): """ Receive update from entity """ if e_event_type(event) == 'move': if self.model.player.level != self.current_level: self.__construct_scene(self.model, self.scene) self.__center_view_on_character(self.model.player) def eventFilter(self, qobject, event): #pylint: disable-msg=C0103 """ Filter events .. Note:: This is done in order to process cursor keys """ result = False if event.type() == QEvent.KeyPress: self.keyPressEvent(event) result = True elif event.type() == QEvent.MouseButtonPress: self.mouseEvent(event) result = True else: result = super().eventFilter(qobject, event) return result def mouseEvent(self, event): """ Handle mouse events """ if self.model.player is None: return player = self.model.player next_creature = self.model.get_next_creature(self.rules_engine) if next_creature == player: point = self.view.mapToScene(event.pos()) x = int(point.x() / 32) y = int(point.y() / 32) player = self.model.player level = player.level location = player.location # if player was clicked, take stairs or open menu if (x, y) == location: if get_portal(level, location): if pyherc.vtable['\ufdd0:is-move-legal'](player, 9): pyherc.vtable['\ufdd0:move'](player, 9) else: self.MenuRequested.emit() elif is_move(event, player, (x, y)): # TODO: maybe moving should be possible with mouse? move(event, player, (x, y)) else: direction = find_direction(location, (x, y)) distance = distance_between(location, (x, y)) if distance == 1: #melee attack? if pyherc.vtable['\ufdd0:is-attack-legal'](player, direction): pyherc.vtable['\ufdd0:attack'](player, direction) else: if (location[0] == x) or (location[1] == y): if pyherc.vtable['\ufdd0:is-attack-legal'](player, direction): pyherc.vtable['\ufdd0:attack'](player, direction) self.process_npc() def keyPressEvent(self, event): #pylint: disable-msg=C0103 """ Handle key events """ if self.model.player is None: return key_code = event.key() player = self.model.player next_creature = self.model.get_next_creature(self.rules_engine) if next_creature == player: if key_code in self.keymap: self.keymap[key_code](key_code, event.modifiers()) self.process_npc() def process_npc(self): """ Process npc characters """ player = self.model.player next_creature = self.model.get_next_creature(self.rules_engine) if next_creature is None: self.model.end_condition = DIED_IN_DUNGEON while (next_creature != player and next_creature is not None and self.model.end_condition == 0): next_creature.act() next_creature = self.model.get_next_creature(self.rules_engine) if next_creature is None: self.model.end_condition = DIED_IN_DUNGEON if self.model.end_condition != 0: self.EndScreenRequested.emit() def _move(self, key, modifiers): """ Process movement key :param key: key triggering the processing :type key: int """ player = self.model.player direction = self.move_key_map[key] if modifiers & Qt.ControlModifier: if direction != 9: pyherc.vtable['\ufdd0:attack'](player, direction) elif modifiers & Qt.AltModifier: if direction != 9: cast(player, direction, 'fireball') else: self.move_controller.move_or_attack(player, direction) def _menu(self, key, modifiers): """ Process menu key :param key: key triggering the processing :type key: int """ self.MenuRequested.emit() def _back(self, key, modifiers): """ Process back key :param key: key triggering the processing :type key: int """ pyherc.vtable['\ufdd0:wait'](self.model.player, Duration.fast) def _zoom_in(self, key, modifiers): """ Zoom map in """ self.view.scale(1.1, 1.1) def _zoom_out(self, key, modifiers): """ Zoom map out """ self.view.scale(0.9, 0.9) def _shoulder_right(self, key, modifiers): """ Process right shoulder button :param key: key triggering the processing :type key: int .. versionadded:: 0.10 """ self.NextSpellRequested.emit() def _shoulder_left(self, key, modifiers): """ Process left shoulder button :param key: key triggering the processing :type key: int .. versionadded:: 0.10 """ self.PreviousSpellRequested.emit() def _action_a(self, key, modifiers): """ Process action a key :param key: key triggering the processing :type key: int """ player = self.model.player level = player.level items = list(get_items(level, player.location)) if items is not None and len(items) > 0: pick_up(player, items[0]) elif pyherc.vtable['\ufdd0:is-move-legal'](player, 9): pyherc.vtable['\ufdd0:move'](player, 9) elif is_dig_legal(player): dig(player)
class OWQualityControl(widget.OWWidget): name = "Quality Control" description = "Experiment quality control" icon = "../widgets/icons/QualityControl.svg" priority = 5000 inputs = [("Experiment Data", Orange.data.Table, "set_data")] outputs = [] DISTANCE_FUNCTIONS = [("Distance from Pearson correlation", dist_pcorr), ("Euclidean distance", dist_eucl), ("Distance from Spearman correlation", dist_spearman) ] settingsHandler = SetContextHandler() split_by_labels = settings.ContextSetting({}) sort_by_labels = settings.ContextSetting({}) selected_distance_index = settings.Setting(0) def __init__(self, parent=None): super().__init__(parent) ## Attributes self.data = None self.distances = None self.groups = None self.unique_pos = None self.base_group_index = 0 ## GUI box = gui.widgetBox(self.controlArea, "Info") self.info_box = gui.widgetLabel(box, "\n") ## Separate By box box = gui.widgetBox(self.controlArea, "Separate By") self.split_by_model = itemmodels.PyListModel(parent=self) self.split_by_view = QListView() self.split_by_view.setSelectionMode(QListView.ExtendedSelection) self.split_by_view.setModel(self.split_by_model) box.layout().addWidget(self.split_by_view) self.split_by_view.selectionModel().selectionChanged.connect( self.on_split_key_changed) ## Sort By box box = gui.widgetBox(self.controlArea, "Sort By") self.sort_by_model = itemmodels.PyListModel(parent=self) self.sort_by_view = QListView() self.sort_by_view.setSelectionMode(QListView.ExtendedSelection) self.sort_by_view.setModel(self.sort_by_model) box.layout().addWidget(self.sort_by_view) self.sort_by_view.selectionModel().selectionChanged.connect( self.on_sort_key_changed) ## Distance box box = gui.widgetBox(self.controlArea, "Distance Measure") gui.comboBox(box, self, "selected_distance_index", items=[name for name, _ in self.DISTANCE_FUNCTIONS], callback=self.on_distance_measure_changed) self.scene = QGraphicsScene() self.scene_view = QGraphicsView(self.scene) self.scene_view.setRenderHints(QPainter.Antialiasing) self.scene_view.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.mainArea.layout().addWidget(self.scene_view) self.scene_view.installEventFilter(self) self._disable_updates = False self._cached_distances = {} self._base_index_hints = {} self.main_widget = None self.resize(800, 600) def clear(self): """Clear the widget state.""" self.data = None self.distances = None self.groups = None self.unique_pos = None with disable_updates(self): self.split_by_model[:] = [] self.sort_by_model[:] = [] self.main_widget = None self.scene.clear() self.info_box.setText("\n") self._cached_distances = {} def set_data(self, data=None): """Set input experiment data.""" self.closeContext() self.clear() self.error(0) self.warning(0) if data is not None: keys = self.get_suitable_keys(data) if not keys: self.error(0, "Data has no suitable feature labels.") data = None self.data = data if data is not None: self.on_new_data() def update_label_candidates(self): """Update the label candidates selection GUI (Group/Sort By views). """ keys = self.get_suitable_keys(self.data) with disable_updates(self): self.split_by_model[:] = keys self.sort_by_model[:] = keys def get_suitable_keys(self, data): """ Return suitable attr label keys from the data where the key has at least two unique values in the data. """ attrs = [attr.attributes.items() for attr in data.domain.attributes] attrs = reduce(operator.iadd, attrs, []) # in case someone put non string values in attributes dict attrs = [(str(key), str(value)) for key, value in attrs] attrs = set(attrs) values = defaultdict(set) for key, value in attrs: values[key].add(value) keys = [key for key in values if len(values[key]) > 1] return keys def selected_split_by_labels(self): """Return the current selected split labels. """ sel_m = self.split_by_view.selectionModel() indices = [r.row() for r in sel_m.selectedRows()] return [self.sort_by_model[i] for i in indices] def selected_sort_by_labels(self): """Return the current selected sort labels """ sel_m = self.sort_by_view.selectionModel() indices = [r.row() for r in sel_m.selectedRows()] return [self.sort_by_model[i] for i in indices] def selected_distance(self): """Return the selected distance function. """ return self.DISTANCE_FUNCTIONS[self.selected_distance_index][1] def selected_base_group_index(self): """Return the selected base group index """ return self.base_group_index def selected_base_indices(self, base_group_index=None): indices = [] for g, ind in self.groups: if base_group_index is None: label = group_label(self.selected_split_by_labels(), g) ind = [i for i in ind if i is not None] i = self._base_index_hints.get(label, ind[0] if ind else None) else: i = ind[base_group_index] indices.append(i) return indices def on_new_data(self): """We have new data and need to recompute all. """ self.closeContext() self.update_label_candidates() self.info_box.setText( "%s genes \n%s experiments" % (len(self.data), len(self.data.domain.attributes))) self.base_group_index = 0 keys = self.get_suitable_keys(self.data) self.openContext(keys) ## Restore saved context settings (split/sort selection) split_by_labels = self.split_by_labels sort_by_labels = self.sort_by_labels def select(model, selection_model, selected_items): """Select items in a Qt item model view """ all_items = list(model) try: indices = [all_items.index(item) for item in selected_items] except: indices = [] for ind in indices: selection_model.select(model.index(ind), QItemSelectionModel.Select) with disable_updates(self): select(self.split_by_view.model(), self.split_by_view.selectionModel(), split_by_labels) select(self.sort_by_view.model(), self.sort_by_view.selectionModel(), sort_by_labels) with widget_disable(self): self.split_and_update() def on_split_key_changed(self, *args): """Split key has changed """ with widget_disable(self): if not self._disable_updates: self.base_group_index = 0 self.split_by_labels = self.selected_split_by_labels() self.split_and_update() def on_sort_key_changed(self, *args): """Sort key has changed """ with widget_disable(self): if not self._disable_updates: self.base_group_index = 0 self.sort_by_labels = self.selected_sort_by_labels() self.split_and_update() def on_distance_measure_changed(self): """Distance measure has changed """ if self.data is not None: with widget_disable(self): self.update_distances() self.replot_experiments() def on_view_resize(self, size): """The view with the quality plot has changed """ if self.main_widget: current = self.main_widget.size() self.main_widget.resize(size.width() - 6, current.height()) self.scene.setSceneRect(self.scene.itemsBoundingRect()) def on_rug_item_clicked(self, item): """An ``item`` in the quality plot has been clicked. """ update = False sort_by_labels = self.selected_sort_by_labels() if sort_by_labels and item.in_group: ## The item is part of the group if item.group_index != self.base_group_index: self.base_group_index = item.group_index update = True else: if sort_by_labels: # If the user clicked on an background item it # invalidates the sorted labels selection with disable_updates(self): self.sort_by_view.selectionModel().clear() update = True index = item.index group = item.group label = group_label(self.selected_split_by_labels(), group) if self._base_index_hints.get(label, 0) != index: self._base_index_hints[label] = index update = True if update: with widget_disable(self): self.split_and_update() def eventFilter(self, obj, event): if obj is self.scene_view and event.type() == QEvent.Resize: self.on_view_resize(event.size()) return super().eventFilter(obj, event) def split_and_update(self): """ Split the data based on the selected sort/split labels and update the quality plot. """ split_labels = self.selected_split_by_labels() sort_labels = self.selected_sort_by_labels() self.warning(0) if not split_labels: self.warning(0, "No separate by label selected.") self.groups, self.unique_pos = \ exp.separate_by(self.data, split_labels, consider=sort_labels, add_empty=True) self.groups = sorted(self.groups.items(), key=lambda t: list(map(float_if_posible, t[0]))) self.unique_pos = sorted( self.unique_pos.items(), key=lambda t: list(map(float_if_posible, t[0]))) if self.groups: if sort_labels: group_base = self.selected_base_group_index() base_indices = self.selected_base_indices(group_base) else: base_indices = self.selected_base_indices() self.update_distances(base_indices) self.replot_experiments() def get_cached_distances(self, measure): if measure not in self._cached_distances: attrs = self.data.domain.attributes mat = numpy.zeros((len(attrs), len(attrs))) self._cached_distances[measure] = \ (mat, set(zip(range(len(attrs)), range(len(attrs))))) return self._cached_distances[measure] def get_cached_distance(self, measure, i, j): matrix, computed = self.get_cached_distances(measure) key = (i, j) if i < j else (j, i) if key in computed: return matrix[i, j] else: return None def get_distance(self, measure, i, j): d = self.get_cached_distance(measure, i, j) if d is None: vec_i = take_columns(self.data, [i]) vec_j = take_columns(self.data, [j]) d = measure(vec_i, vec_j) mat, computed = self.get_cached_distances(measure) mat[i, j] = d key = key = (i, j) if i < j else (j, i) computed.add(key) return d def store_distance(self, measure, i, j, dist): matrix, computed = self.get_cached_distances(measure) key = (i, j) if i < j else (j, i) matrix[j, i] = matrix[i, j] = dist computed.add(key) def update_distances(self, base_indices=()): """Recompute the experiment distances. """ distance = self.selected_distance() if base_indices == (): base_group_index = self.selected_base_group_index() base_indices = [ind[base_group_index] \ for _, ind in self.groups] assert (len(base_indices) == len(self.groups)) base_distances = [] attributes = self.data.domain.attributes pb = gui.ProgressBar(self, len(self.groups) * len(attributes)) for (group, indices), base_index in zip(self.groups, base_indices): # Base column of the group if base_index is not None: base_vec = take_columns(self.data, [base_index]) distances = [] # Compute the distances between base column # and all the rest data columns. for i in range(len(attributes)): if i == base_index: distances.append(0.0) elif self.get_cached_distance(distance, i, base_index) is not None: distances.append( self.get_cached_distance(distance, i, base_index)) else: vec_i = take_columns(self.data, [i]) dist = distance(base_vec, vec_i) self.store_distance(distance, i, base_index, dist) distances.append(dist) pb.advance() base_distances.append(distances) else: base_distances.append(None) pb.finish() self.distances = base_distances def replot_experiments(self): """Replot the whole quality plot. """ self.scene.clear() labels = [] max_dist = numpy.nanmax(list(filter(None, self.distances))) rug_widgets = [] group_pen = QPen(Qt.black) group_pen.setWidth(2) group_pen.setCapStyle(Qt.RoundCap) background_pen = QPen(QColor(0, 0, 250, 150)) background_pen.setWidth(1) background_pen.setCapStyle(Qt.RoundCap) main_widget = QGraphicsWidget() layout = QGraphicsGridLayout() attributes = self.data.domain.attributes if self.data is not None: for (group, indices), dist_vec in zip(self.groups, self.distances): indices_set = set(indices) rug_items = [] if dist_vec is not None: for i, attr in enumerate(attributes): # Is this a within group distance or background in_group = i in indices_set if in_group: rug_item = ClickableRugItem( dist_vec[i] / max_dist, 1.0, self.on_rug_item_clicked) rug_item.setPen(group_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group_index = indices.index(i) rug_item.setZValue(rug_item.zValue() + 1) else: rug_item = ClickableRugItem( dist_vec[i] / max_dist, 0.85, self.on_rug_item_clicked) rug_item.setPen(background_pen) tooltip = experiment_description(attr) rug_item.setToolTip(tooltip) rug_item.group = group rug_item.index = i rug_item.in_group = in_group rug_items.append(rug_item) rug_widget = RugGraphicsWidget(parent=main_widget) rug_widget.set_rug(rug_items) rug_widgets.append(rug_widget) label = group_label(self.selected_split_by_labels(), group) label_item = QGraphicsSimpleTextItem(label, main_widget) label_item = GraphicsSimpleTextLayoutItem(label_item, parent=layout) label_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) labels.append(label_item) for i, (label, rug_w) in enumerate(zip(labels, rug_widgets)): layout.addItem(label, i, 0, Qt.AlignVCenter) layout.addItem(rug_w, i, 1) layout.setRowMaximumHeight(i, 30) main_widget.setLayout(layout) self.scene.addItem(main_widget) self.main_widget = main_widget self.rug_widgets = rug_widgets self.labels = labels self.on_view_resize(self.scene_view.size())
class PlayMapWidget(QWidget): """ Widget for displaying playing world .. versionadded:: 0.5 """ def __init__(self, parent, model, surface_manager, action_factory, rng, rules_engine, configuration): """ Default constructor """ super().__init__(parent) self.model = model self.scene = None self.surface_manager = surface_manager self.action_factory = action_factory self.rng = rng self.rules_engine = rules_engine self.configuration = configuration self.current_level = None self.view = None self.animation_adapters = [] self.animation_timers = [] for adapter in range(10): self.animation_adapters.append(TimerAdapter()) self.animation_timers.append(QTimer(self)) self.animation_timers[adapter].timeout.connect( self.animation_adapters[adapter].trigger_animations) self.animation_timers[adapter].start(450 + adapter * 10) self.animations = [] self.move_controller = MoveController(action_factory=action_factory, rng=rng) self.__set_layout() self.keymap, self.move_key_map = self._construct_keymaps( configuration.controls) self.animation_factory = AnimationFactory() MenuRequested = pyqtSignal(name='MenuRequested') EndScreenRequested = pyqtSignal(name='EndScreenRequested') NextSpellRequested = pyqtSignal(name='NextSpellRequested') PreviousSpellRequested = pyqtSignal(name='PreviousSpellRequested') def _construct_keymaps(self, config): """ Construct keymaps for handling input """ keymap = {} move_keymap = {} for key in config.move_left: keymap[key] = self._move move_keymap[key] = 7 for key in config.move_up: keymap[key] = self._move move_keymap[key] = 1 for key in config.move_right: keymap[key] = self._move move_keymap[key] = 3 for key in config.move_down: keymap[key] = self._move move_keymap[key] = 5 for key in config.start: keymap[key] = self._menu for key in config.action_a: keymap[key] = self._action_a for key in config.back: keymap[key] = self._back for key in config.left_shoulder: keymap[key] = self._shoulder_left for key in config.right_shoulder: keymap[key] = self._shoulder_right for key in config.mode_1: keymap[key] = self._zoom_out for key in config.mode_2: keymap[key] = self._zoom_in return keymap, move_keymap def __set_layout(self): """ Set layout of this widget """ self.scene = QGraphicsScene() layout = QHBoxLayout() self.view = QGraphicsView(self.scene) self.view.setFocusPolicy(Qt.StrongFocus) self.view.installEventFilter(self) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) layout.addWidget(self.view) self.setLayout(layout) def construct_scene(self): """ Construct scene to display """ self.__construct_scene(self.model, self.scene) self.model.player.register_for_updates(self) self.model.register_event_listener(self) self.__center_view_on_character(self.model.player) def __center_view_on_character(self, entity): """ Center view on given entity """ location = entity.location width = 32 height = 32 self.view.setSceneRect((location[0] * 32) - width // 2, (location[1] * 32) - height // 2, width, height) def __construct_scene(self, model, scene): """ Constructs scene to display """ for anim in [x for x in self.animations]: anim.stop() anim.clear() for adapter in self.animation_adapters: adapter.glyphs.clear() self.animations = [] scene.clear() self.current_level = model.player.level for location, tile in get_tiles(self.current_level): if tile['\ufdd0:floor']: new_glyph = MapGlyph( self.surface_manager.get_icon(tile['\ufdd0:floor']), None, self.animation_adapters[0]) new_glyph.setZValue(zorder_floor) new_glyph.setPos(location[0] * 32, location[1] * 32) scene.addItem(new_glyph) if tile['\ufdd0:wall']: new_glyph = MapGlyph( self.surface_manager.get_icon(tile['\ufdd0:wall']), None) new_glyph.setZValue(zorder_wall) new_glyph.setPos(location[0] * 32, location[1] * 32) scene.addItem(new_glyph) for tile_id in tile['\ufdd0:ornamentation']: new_glyph = MapGlyph(self.surface_manager.get_icon(tile_id), None, self.rng.choice(self.animation_adapters)) new_glyph.setZValue(zorder_ornament) new_glyph.setPos(location[0] * 32, location[1] * 32) scene.addItem(new_glyph) for item in tile['\ufdd0:items']: self.add_glyph(item, scene, zorder_item) for trap in tile['\ufdd0:traps']: self.add_glyph(trap, scene, zorder_trap) for creature in get_characters(self.current_level): self.add_glyph(creature, scene, zorder_character) def add_glyph(self, entity, scene, z_order): """ Add graphical representation of an entity :param entity: entity to display :param scene: scene where glyph will be added :type scene: QGraphicsScene :param z_order: z-order of entity being displayed :type z_order: int """ new_glyph = MapGlyph(self.surface_manager.get_icon(entity.icon), entity, self.rng.choice(self.animation_adapters)) new_glyph.setZValue(z_order) new_glyph.setPos(entity.location[0] * 32, entity.location[1] * 32) scene.addItem(new_glyph) def remove_glyph(self, entity): """ Remove graphical representation of an entity """ glyphs = [ x for x in self.view.items() if (hasattr(x, 'entity')) and (x.entity == entity) ] for glyph in glyphs: self.view.scene().removeItem(glyph) def receive_event(self, event): """ Receive event from model """ anim = self.animation_factory.create_animation(event) anim.trigger(self) def remove_finished_animation(self): """ Remove finished animation """ finished_animations = [ x for x in self.animations if x.state() == QAbstractAnimation.Stopped ] counters = [ x.animationAt(0).targetObject().object_to_animate for x in finished_animations ] #TODO: works only if single thing is animated for item in finished_animations: item.clear() self.animations.remove(item) def receive_update(self, event): """ Receive update from entity """ if e_event_type(event) == 'move': if self.model.player.level != self.current_level: self.__construct_scene(self.model, self.scene) self.__center_view_on_character(self.model.player) def eventFilter(self, qobject, event): #pylint: disable-msg=C0103 """ Filter events .. Note:: This is done in order to process cursor keys """ result = False if event.type() == QEvent.KeyPress: self.keyPressEvent(event) result = True elif event.type() == QEvent.MouseButtonPress: self.mouseEvent(event) result = True else: result = super().eventFilter(qobject, event) return result def mouseEvent(self, event): """ Handle mouse events """ if self.model.player is None: return player = self.model.player next_creature = self.model.get_next_creature(self.rules_engine) if next_creature == player: point = self.view.mapToScene(event.pos()) x = int(point.x() / 32) y = int(point.y() / 32) player = self.model.player level = player.level location = player.location # if player was clicked, take stairs or open menu if (x, y) == location: if get_portal(level, location): if pyherc.vtable['\ufdd0:is-move-legal'](player, 9): pyherc.vtable['\ufdd0:move'](player, 9) else: self.MenuRequested.emit() elif is_move( event, player, (x, y)): # TODO: maybe moving should be possible with mouse? move(event, player, (x, y)) else: direction = find_direction(location, (x, y)) distance = distance_between(location, (x, y)) if distance == 1: #melee attack? if pyherc.vtable['\ufdd0:is-attack-legal'](player, direction): pyherc.vtable['\ufdd0:attack'](player, direction) else: if (location[0] == x) or (location[1] == y): if pyherc.vtable['\ufdd0:is-attack-legal'](player, direction): pyherc.vtable['\ufdd0:attack'](player, direction) self.process_npc() def keyPressEvent(self, event): #pylint: disable-msg=C0103 """ Handle key events """ if self.model.player is None: return key_code = event.key() player = self.model.player next_creature = self.model.get_next_creature(self.rules_engine) if next_creature == player: if key_code in self.keymap: self.keymap[key_code](key_code, event.modifiers()) self.process_npc() def process_npc(self): """ Process npc characters """ player = self.model.player next_creature = self.model.get_next_creature(self.rules_engine) if next_creature is None: self.model.end_condition = DIED_IN_DUNGEON while (next_creature != player and next_creature is not None and self.model.end_condition == 0): next_creature.act() next_creature = self.model.get_next_creature(self.rules_engine) if next_creature is None: self.model.end_condition = DIED_IN_DUNGEON if self.model.end_condition != 0: self.EndScreenRequested.emit() def _move(self, key, modifiers): """ Process movement key :param key: key triggering the processing :type key: int """ player = self.model.player direction = self.move_key_map[key] if modifiers & Qt.ControlModifier: if direction != 9: pyherc.vtable['\ufdd0:attack'](player, direction) elif modifiers & Qt.AltModifier: if direction != 9: cast(player, direction, 'fireball') else: self.move_controller.move_or_attack(player, direction) def _menu(self, key, modifiers): """ Process menu key :param key: key triggering the processing :type key: int """ self.MenuRequested.emit() def _back(self, key, modifiers): """ Process back key :param key: key triggering the processing :type key: int """ pyherc.vtable['\ufdd0:wait'](self.model.player, Duration.fast) def _zoom_in(self, key, modifiers): """ Zoom map in """ self.view.scale(1.1, 1.1) def _zoom_out(self, key, modifiers): """ Zoom map out """ self.view.scale(0.9, 0.9) def _shoulder_right(self, key, modifiers): """ Process right shoulder button :param key: key triggering the processing :type key: int .. versionadded:: 0.10 """ self.NextSpellRequested.emit() def _shoulder_left(self, key, modifiers): """ Process left shoulder button :param key: key triggering the processing :type key: int .. versionadded:: 0.10 """ self.PreviousSpellRequested.emit() def _action_a(self, key, modifiers): """ Process action a key :param key: key triggering the processing :type key: int """ player = self.model.player level = player.level items = list(get_items(level, player.location)) if items is not None and len(items) > 0: pick_up(player, items[0]) elif pyherc.vtable['\ufdd0:is-move-legal'](player, 9): pyherc.vtable['\ufdd0:move'](player, 9) elif is_dig_legal(player): dig(player)