class NumberElement(FormElement): """ A form element representing a number The shorthand is a tuple of 2 or 3 numbers: (min, max) or (min, max default):: e = FormElement.auto((0., 1.)) """ state = ValueProperty('ui') @classmethod def recognizes(cls, params): try: if len(params) not in [2, 3]: return False return all(isinstance(p, six.integer_types + (float,)) for p in params) except TypeError: return False def _build_ui(self): w = LabeledSlider(*self.params[:3]) w.valueChanged.connect(nonpartial(self.changed)) return w def value(self, layer=None, view=None): return self.ui.value()
class TextBoxElement(FormElement): """ A form element representing a generic textbox The shorthand is any string starting with an _.:: e = FormElement.auto("_default") Everything after the underscore is taken as the default value. """ state = ValueProperty('ui') def _build_ui(self): self._widget = GenericTextBox() self._widget.textChanged.connect(nonpartial(self.changed)) self.set_value(self.params[1:]) return self._widget def value(self, layer=None, view=None): return self._widget.text() def set_value(self, val): self._widget.setText(str(val)) @classmethod def recognizes(cls, params): try: if isinstance(params, str) & params.startswith('_'): return True except AttributeError: return None
class FloatElement(FormElement): """ A form element representing a generic number box. The shorthand is any number:: e = FormElement.auto(2) The number itself is taken as the default value. """ state = ValueProperty('ui') def _build_ui(self): self._widget = GenericTextBox() self._widget.textChanged.connect(nonpartial(self.changed)) self.set_value(self.params) return self._widget def value(self, layer=None, view=None): try: return float(self._widget.text()) except ValueError: return None def set_value(self, val): self._widget.setText(str(val)) @classmethod def recognizes(cls, params): return isinstance(params, (int, float)) and not isinstance(params, bool)
class ScatterLayerStyleWidget(QtGui.QWidget): size = ValueProperty('ui.value_size') symbol = CurrentComboProperty('ui.combo_symbol') alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1)) def __init__(self, layer_artist): super(ScatterLayerStyleWidget, self).__init__() self.ui = load_ui('layer_style_widget.ui', self, directory=os.path.dirname(__file__)) self._setup_symbol_combo() self.layer = layer_artist.layer # Set up connections self._connect_global() # Set initial values self.symbol = self.layer.style.marker self.size = self.layer.style.markersize self.ui.label_color.setColor(self.layer.style.color) self.alpha = self.layer.style.alpha def _connect_global(self): connect_current_combo(self.layer.style, 'marker', self.ui.combo_symbol) connect_value(self.layer.style, 'markersize', self.ui.value_size) connect_color(self.layer.style, 'color', self.ui.label_color) connect_value(self.layer.style, 'alpha', self.ui.slider_alpha, value_range=(0, 1)) def _setup_symbol_combo(self): self._symbols = list(POINT_ICONS.keys()) for idx, symbol in enumerate(self._symbols): icon = symbol_icon(symbol) self.ui.combo_symbol.addItem(icon, '', userData=symbol) self.ui.combo_symbol.setIconSize(QtCore.QSize(20, 20)) self.ui.combo_symbol.setMinimumSize(10, 32)
class HistogramLayerStyleWidget(QtWidgets.QWidget): alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1)) def __init__(self, layer_artist): super(HistogramLayerStyleWidget, self).__init__() self.ui = load_ui('layer_style_widget.ui', self, directory=os.path.dirname(__file__)) self.layer = layer_artist.layer # Set up connections self._connect_global() # Set initial values self.ui.label_color.setColor(self.layer.style.color) self.alpha = self.layer.style.alpha def _connect_global(self): connect_color(self.layer.style, 'color', self.ui.label_color) connect_value(self.layer.style, 'alpha', self.ui.slider_alpha, value_range=(0, 1))
class HistogramWidget(DataViewer): LABEL = "Histogram" _property_set = DataViewer._property_set + \ 'component xlog ylog normed cumulative autoscale xmin xmax nbins'.split( ) xmin = FloatLineProperty('ui.xmin', 'Minimum value') xmax = FloatLineProperty('ui.xmax', 'Maximum value') normed = ButtonProperty('ui.normalized_box', 'Normalized?') autoscale = ButtonProperty('ui.autoscale_box', 'Autoscale view to histogram?') cumulative = ButtonProperty('ui.cumulative_box', 'Cumulative?') nbins = ValueProperty('ui.binSpinBox', 'Number of bins') xlog = ButtonProperty('ui.xlog_box', 'Log-scale the x axis?') ylog = ButtonProperty('ui.ylog_box', 'Log-scale the y axis?') def __init__(self, session, parent=None): super(HistogramWidget, self).__init__(session, parent) self.central_widget = MplWidget() self.setCentralWidget(self.central_widget) self.option_widget = QtGui.QWidget() self.ui = load_ui('options_widget.ui', self.option_widget, directory=os.path.dirname(__file__)) self._tweak_geometry() self.client = HistogramClient(self._data, self.central_widget.canvas.fig, layer_artist_container=self._layer_artist_container) self._init_limits() self.make_toolbar() self._connect() # maps _hash(componentID) -> componentID self._component_hashes = {} @staticmethod def _get_default_tools(): return [] def _init_limits(self): validator = QtGui.QDoubleValidator(None) validator.setDecimals(7) self.ui.xmin.setValidator(validator) self.ui.xmax.setValidator(validator) lo, hi = self.client.xlimits self.ui.xmin.setText(str(lo)) self.ui.xmax.setText(str(hi)) def _tweak_geometry(self): self.central_widget.resize(600, 400) self.resize(self.central_widget.size()) def _connect(self): ui = self.ui cl = self.client ui.attributeCombo.currentIndexChanged.connect(self._set_attribute_from_combo) ui.normalized_box.toggled.connect(partial(setattr, cl, 'normed')) ui.autoscale_box.toggled.connect(partial(setattr, cl, 'autoscale')) ui.cumulative_box.toggled.connect(partial(setattr, cl, 'cumulative')) connect_int_spin(cl, 'nbins', ui.binSpinBox) connect_float_edit(cl, 'xmin', ui.xmin) connect_float_edit(cl, 'xmax', ui.xmax) connect_bool_button(cl, 'xlog', ui.xlog_box) connect_bool_button(cl, 'ylog', ui.ylog_box) def make_toolbar(self): result = GlueToolbar(self.central_widget.canvas, self, name='Histogram') for mode in self._mouse_modes(): result.add_mode(mode) self.addToolBar(result) return result def _mouse_modes(self): axes = self.client.axes def apply_mode(mode): return self.apply_roi(mode.roi()) rect = HRangeMode(axes, roi_callback=apply_mode) return [rect] @defer_draw def _update_attributes(self): """Repopulate the combo box that selects the quantity to plot""" combo = self.ui.attributeCombo component = self.component new = self.client.component or component combo.blockSignals(True) combo.clear() # implementation note: # PySide doesn't robustly store python objects with setData # use _hash(x) instead model = QtGui.QStandardItemModel() data_ids = set(_hash(d) for d in self._data) self._component_hashes = dict((_hash(c), c) for d in self._data for c in d.components) found = False for d in self._data: if d not in self._layer_artist_container: continue item = QtGui.QStandardItem(d.label) item.setData(_hash(d), role=Qt.UserRole) assert item.data(Qt.UserRole) == _hash(d) item.setFlags(item.flags() & ~Qt.ItemIsEnabled) model.appendRow(item) for c in d.visible_components: if (not d.get_component(c).categorical and not d.get_component(c).numeric): continue if c is new: found = True item = QtGui.QStandardItem(c.label) item.setData(_hash(c), role=Qt.UserRole) model.appendRow(item) combo.setModel(model) # separators below data items for i in range(combo.count()): if combo.itemData(i) in data_ids: combo.insertSeparator(i + 1) combo.blockSignals(False) if found: self.component = new else: combo.setCurrentIndex(2) # skip first data + separator self._set_attribute_from_combo() @property def component(self): combo = self.ui.attributeCombo index = combo.currentIndex() return self._component_hashes.get(combo.itemData(index), None) @component.setter def component(self, component): combo = self.ui.attributeCombo if combo.count() == 0: # cold start problem, when restoring self._update_attributes() # combo.findData doesn't seem to work robustly for i in range(combo.count()): data = combo.itemData(i) if data == _hash(component): combo.setCurrentIndex(i) return raise IndexError("Component not present: %s" % component) @defer_draw def _set_attribute_from_combo(self, *args): if self.component is not None: for d in self._data: try: component = d.get_component(self.component) except: continue else: break if component.categorical: if self.ui.xlog_box.isEnabled(): self.ui.xlog_box.setEnabled(False) self.xlog = False else: if not self.ui.xlog_box.isEnabled(): self.ui.xlog_box.setEnabled(True) self.client.set_component(self.component) self.update_window_title() @defer_draw def add_data(self, data): """ Add data item to combo box. If first addition, also update attributes """ if self.data_present(data): return True if data.size > WARN_SLOW and not self._confirm_large_data(data): return False self.client.add_layer(data) self._update_attributes() return True def add_subset(self, subset): pass def _remove_data(self, data): """ Remove data item from the combo box """ pass def data_present(self, data): return data in self._layer_artist_container def register_to_hub(self, hub): super(HistogramWidget, self).register_to_hub(hub) self.client.register_to_hub(hub) hub.subscribe(self, msg.DataCollectionDeleteMessage, handler=lambda x: self._remove_data(x.data)) hub.subscribe(self, msg.DataUpdateMessage, handler=lambda *args: self._update_labels()) hub.subscribe(self, msg.ComponentsChangedMessage, handler=lambda x: self._update_attributes()) def unregister(self, hub): super(HistogramWidget, self).unregister(hub) self.client.unregister(hub) hub.unsubscribe_all(self) @property def window_title(self): c = self.client.component if c is not None: label = str(c.label) else: label = 'Histogram' return label def _update_labels(self): self.update_window_title() self._update_attributes() def __str__(self): return "Histogram Widget" def options_widget(self): return self.option_widget
class ScatterLayerStyleWidget(QtWidgets.QWidget): # Size-related GUI elements size_mode = CurrentComboTextProperty('ui.combo_size_mode') size_attribute = CurrentComboProperty('ui.combo_size_attribute') size_vmin = FloatLineProperty('ui.value_size_vmin') size_vmax = FloatLineProperty('ui.value_size_vmax') size = FloatLineProperty('ui.value_fixed_size') try: size_scaling = ValueProperty('ui.slider_size_scaling', value_range=(0.1, 10), log=True) except TypeError: # Glue < 0.8 size_scaling = ValueProperty('ui.slider_size_scaling') # Color-related GUI elements color_mode = CurrentComboTextProperty('ui.combo_color_mode') cmap_attribute = CurrentComboProperty('ui.combo_cmap_attribute') cmap_vmin = FloatLineProperty('ui.value_cmap_vmin') cmap_vmax = FloatLineProperty('ui.value_cmap_vmax') cmap = CurrentComboProperty('ui.combo_cmap') alpha = ValueProperty('ui.slider_alpha') def __init__(self, layer_artist): super(ScatterLayerStyleWidget, self).__init__() self.ui = load_ui('layer_style_widget.ui', self, directory=os.path.dirname(__file__)) self.layer_artist = layer_artist self.layer = layer_artist.layer # Set up size and color options self._setup_size_options() self._setup_color_options() self._connect_global() # Set initial values self.layer_artist.size = self.layer.style.markersize self.layer_artist.size_scaling = 1 self.layer_artist.size_mode = 'fixed' self.size_mode = 'Fixed' self.layer_artist.color = self.layer.style.color self.layer_artist.alpha = self.layer.style.alpha self.layer_artist.color_mode = 'fixed' self.color_mode = 'Fixed' self.ui.combo_size_attribute.setCurrentIndex(0) self.ui.combo_cmap_attribute.setCurrentIndex(0) self.ui.combo_cmap.setCurrentIndex(0) self.layer_artist.visible = True self._update_size_mode() self._update_color_mode() def _connect_global(self): connect_float_edit(self.layer.style, 'markersize', self.ui.value_fixed_size) connect_color(self.layer.style, 'color', self.ui.label_color) connect_value(self.layer.style, 'alpha', self.ui.slider_alpha, value_range=(0, 1)) def _disconnect_global(self): # FIXME: Requires the ability to disconnect connections pass def _setup_size_options(self): # Set up attribute list label_data = [(comp.label, comp) for comp in self.visible_components] update_combobox(self.ui.combo_size_attribute, label_data) # Set up connections with layer artist connect_float_edit(self.layer_artist, 'size', self.ui.value_fixed_size) connect_current_combo(self.layer_artist, 'size_attribute', self.ui.combo_size_attribute) connect_float_edit(self.layer_artist, 'size_vmin', self.ui.value_size_vmin) connect_float_edit(self.layer_artist, 'size_vmax', self.ui.value_size_vmax) connect_value(self.layer_artist, 'size_scaling', self.ui.slider_size_scaling, value_range=(0.1, 10), log=True) # Set up internal connections self.ui.combo_size_mode.currentIndexChanged.connect(self._update_size_mode) self.ui.combo_size_attribute.currentIndexChanged.connect(self._update_size_limits) self.ui.button_flip_size.clicked.connect(self._flip_size) def _update_size_mode(self): self.layer_artist.size_mode = self.size_mode.lower() if self.size_mode == "Fixed": self.ui.size_row_2.hide() self.ui.combo_size_attribute.hide() self.ui.value_fixed_size.show() else: self.ui.value_fixed_size.hide() self.ui.combo_size_attribute.show() self.ui.size_row_2.show() def _update_size_limits(self): if not hasattr(self, '_size_limits'): self._size_limits = {} if self.size_attribute in self._size_limits: self.size_vmin, self.size_vmax = self._size_limits[self.size_attribute] else: self.size_vmin, self.size_vmax = self.default_limits(self.size_attribute) self._size_limits[self.size_attribute] = self.size_vmin, self.size_vmax def _flip_size(self): self.size_vmin, self.size_vmax = self.size_vmax, self.size_vmin def _setup_color_options(self): # Set up attribute list label_data = [(comp.label, comp) for comp in self.visible_components] update_combobox(self.ui.combo_cmap_attribute, label_data) # Set up connections with layer artist connect_color(self.layer_artist, 'color', self.ui.label_color) connect_current_combo(self.layer_artist, 'cmap_attribute', self.ui.combo_cmap_attribute) connect_float_edit(self.layer_artist, 'cmap_vmin', self.ui.value_cmap_vmin) connect_float_edit(self.layer_artist, 'cmap_vmax', self.ui.value_cmap_vmax) connect_current_combo(self.layer_artist, 'cmap', self.ui.combo_cmap) connect_value(self.layer_artist, 'alpha', self.ui.slider_alpha, value_range=(0, 1)) # Set up internal connections self.ui.combo_color_mode.currentIndexChanged.connect(self._update_color_mode) self.ui.combo_cmap_attribute.currentIndexChanged.connect(self._update_cmap_limits) self.ui.button_flip_cmap.clicked.connect(self._flip_cmap) def _update_color_mode(self): self.layer_artist.color_mode = self.color_mode.lower() if self.color_mode == "Fixed": self.ui.color_row_2.hide() self.ui.color_row_3.hide() self.ui.combo_cmap_attribute.hide() self.ui.spacer_color_label.show() self.ui.label_color.show() else: self.ui.label_color.hide() self.ui.combo_cmap_attribute.show() self.ui.spacer_color_label.hide() self.ui.color_row_2.show() self.ui.color_row_3.show() def _update_cmap_limits(self): if not hasattr(self, '_cmap_limits'): self._cmap_limits = {} if self.cmap_attribute in self._cmap_limits: self.cmap_vmin, self.cmap_vmax = self._cmap_limits[self.cmap_attribute] else: self.cmap_vmin, self.cmap_vmax = self.default_limits(self.cmap_attribute) self._cmap_limits[self.cmap_attribute] = self.cmap_vmin, self.cmap_vmax def _flip_cmap(self): self.cmap_vmin, self.cmap_vmax = self.cmap_vmax, self.cmap_vmin def default_limits(self, attribute): # For subsets, we want to compute the limits based on the full # dataset not just the subset. if isinstance(self.layer, Subset): vmin = np.nanmin(self.layer.data[attribute]) vmax = np.nanmax(self.layer.data[attribute]) else: vmin = np.nanmin(self.layer[attribute]) vmax = np.nanmax(self.layer[attribute]) return vmin, vmax @property def visible_components(self): if isinstance(self.layer, Subset): return self.layer.data.visible_components else: return self.layer.visible_components
class SubsetFacet(QtGui.QDialog): log = ButtonProperty('ui.checkbox_log') vmin = FloatLineProperty('ui.value_min') vmax = FloatLineProperty('ui.value_max') steps = ValueProperty('ui.value_n_subsets') data = Pointer('ui.component_selector.data') component = Pointer('ui.component_selector.component') def __init__(self, collect, default=None, parent=None): """Create a new dialog for subset faceting :param collect: The :class:`~glue.core.data_collection.DataCollection` to use :param default: The default dataset in the collection (optional) """ super(SubsetFacet, self).__init__(parent=parent) self.ui = load_ui('subset_facet.ui', self, directory=os.path.dirname(__file__)) self.ui.setWindowTitle("Subset Facet") self._collect = collect self.ui.component_selector.setup(self._collect) if default is not None: self.ui.component_selector.data = default val = QtGui.QDoubleValidator(-1e100, 1e100, 4, None) self.ui.component_selector.component_changed.connect(self._set_limits) combo = self.ui.color_scale for cmap in [cm.cool, cm.RdYlBu, cm.RdYlGn, cm.RdBu, cm.Purples]: combo.addItem(QtGui.QIcon(cmap2pixmap(cmap)), cmap.name, cmap) def _set_limits(self): data = self.ui.component_selector.data cid = self.ui.component_selector.component vals = data[cid] wmin = self.ui.value_min wmax = self.ui.value_max wmin.setText(pretty_number(np.nanmin(vals))) wmax.setText(pretty_number(np.nanmax(vals))) @property def cmap(self): combo = self.ui.color_scale index = combo.currentIndex() return combo.itemData(index) def _apply(self): try: lo, hi = self.vmin, self.vmax except ValueError: return # limits not set. Abort if not np.isfinite(lo) or not np.isfinite(hi): return subsets = facet_subsets(self._collect, self.component, lo=lo, hi=hi, steps=self.steps, log=self.log) colorize_subsets(subsets, self.cmap) @classmethod def facet(cls, collect, default=None, parent=None): """Class method to create facted subsets The arguments are the same as __init__ """ self = cls(collect, parent=parent, default=default) value = self.exec_() if value == QtGui.QDialog.Accepted: self._apply()
class SliceWidget(QtWidgets.QWidget): label = TextProperty('_ui_label') slider_label = TextProperty('_ui_slider.label') slider_unit = TextProperty('_ui_slider.text_unit') slice_center = ValueProperty('_ui_slider.slider') mode = CurrentComboProperty('_ui_mode') use_world = ButtonProperty('_ui_slider.checkbox_world') slice_changed = QtCore.Signal(int) mode_changed = QtCore.Signal(str) def __init__(self, label='', world=None, lo=0, hi=10, parent=None, aggregation=None, world_unit=None, world_warning=False): super(SliceWidget, self).__init__(parent) if aggregation is not None: raise NotImplemented("Aggregation option not implemented") self._world = np.asarray(world) self._world_warning = world_warning self._world_unit = world_unit layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(3, 1, 3, 1) layout.setSpacing(0) top = QtWidgets.QHBoxLayout() top.setContentsMargins(3, 3, 3, 3) label = QtWidgets.QLabel(label) top.addWidget(label) mode = QtWidgets.QComboBox() mode.addItem('x', 'x') mode.addItem('y', 'y') mode.addItem('slice', 'slice') mode.currentIndexChanged.connect( lambda x: self.mode_changed.emit(self.mode)) mode.currentIndexChanged.connect(self._update_mode) top.addWidget(mode) layout.addLayout(top) slider = load_ui('data_slice_widget.ui', None, directory=os.path.dirname(__file__)) self._ui_slider = slider font = slider.label_warning.font() font.setPointSize(font.pointSize() * 0.75) slider.label_warning.setFont(font) slider.button_first.setStyleSheet('border: 0px') slider.button_first.setIcon(get_icon('playback_first')) slider.button_prev.setStyleSheet('border: 0px') slider.button_prev.setIcon(get_icon('playback_prev')) slider.button_back.setStyleSheet('border: 0px') slider.button_back.setIcon(get_icon('playback_back')) slider.button_stop.setStyleSheet('border: 0px') slider.button_stop.setIcon(get_icon('playback_stop')) slider.button_forw.setStyleSheet('border: 0px') slider.button_forw.setIcon(get_icon('playback_forw')) slider.button_next.setStyleSheet('border: 0px') slider.button_next.setIcon(get_icon('playback_next')) slider.button_last.setStyleSheet('border: 0px') slider.button_last.setIcon(get_icon('playback_last')) slider.slider.setMinimum(lo) slider.slider.setMaximum(hi) slider.slider.setValue((lo + hi) / 2) slider.slider.valueChanged.connect( lambda x: self.slice_changed.emit(self.mode)) slider.slider.valueChanged.connect( nonpartial(self.set_label_from_slider)) slider.label.setMinimumWidth(80) slider.label.setText(str(slider.slider.value())) slider.label.editingFinished.connect( nonpartial(self.set_slider_from_label)) self._play_timer = QtCore.QTimer() self._play_timer.setInterval(500) self._play_timer.timeout.connect(nonpartial(self._play_slice)) slider.button_first.clicked.connect( nonpartial(self._browse_slice, 'first')) slider.button_prev.clicked.connect( nonpartial(self._browse_slice, 'prev')) slider.button_back.clicked.connect( nonpartial(self._adjust_play, 'back')) slider.button_stop.clicked.connect( nonpartial(self._adjust_play, 'stop')) slider.button_forw.clicked.connect( nonpartial(self._adjust_play, 'forw')) slider.button_next.clicked.connect( nonpartial(self._browse_slice, 'next')) slider.button_last.clicked.connect( nonpartial(self._browse_slice, 'last')) slider.checkbox_world.toggled.connect( nonpartial(self.set_label_from_slider)) if world is None: self.use_world = False slider.checkbox_world.hide() else: self.use_world = not world_warning if world_unit: self.slider_unit = world_unit else: self.slider_unit = '' layout.addWidget(slider) self.setLayout(layout) self._ui_label = label self._ui_mode = mode self._update_mode() self._frozen = False self._play_speed = 0 self.set_label_from_slider() def set_label_from_slider(self): value = self._ui_slider.slider.value() if self.use_world: text = str(self._world[value]) if self._world_warning: self._ui_slider.label_warning.show() else: self._ui_slider.label_warning.hide() self.slider_unit = self._world_unit else: text = str(value) self._ui_slider.label_warning.hide() self.slider_unit = '' self._ui_slider.label.setText(text) def set_slider_from_label(self): text = self._ui_slider.label.text() if self.use_world: # Don't want to assume world is sorted, pick closest value value = np.argmin(np.abs(self._world - float(text))) self._ui_slider.label.setText(str(self._world[value])) else: value = int(text) self._ui_slider.slider.setValue(value) def _adjust_play(self, action): if action == 'stop': self._play_speed = 0 elif action == 'back': if self._play_speed > 0: self._play_speed = -1 else: self._play_speed -= 1 elif action == 'forw': if self._play_speed < 0: self._play_speed = +1 else: self._play_speed += 1 if self._play_speed == 0: self._play_timer.stop() else: self._play_timer.start() self._play_timer.setInterval(500 / abs(self._play_speed)) def _play_slice(self): if self._play_speed > 0: self._browse_slice('next', play=True) elif self._play_speed < 0: self._browse_slice('prev', play=True) def _browse_slice(self, action, play=False): imin = self._ui_slider.slider.minimum() imax = self._ui_slider.slider.maximum() value = self._ui_slider.slider.value() # If this was not called from _play_slice, we should stop the # animation. if not play: self._adjust_play('stop') if action == 'first': value = imin elif action == 'last': value = imax elif action == 'prev': value = value - 1 if value < imin: value = imax elif action == 'next': value = value + 1 if value > imax: value = imin else: raise ValueError("Action should be one of first/prev/next/last") self._ui_slider.slider.setValue(value) def _update_mode(self, *args): if self.mode != 'slice': self._ui_slider.hide() self._adjust_play('stop') else: self._ui_slider.show() def freeze(self): self.mode = 'slice' self._ui_mode.setEnabled(False) self._ui_slider.hide() self._frozen = True @property def frozen(self): return self._frozen
class IsosurfaceLayerStyleWidget(QtWidgets.QWidget): # GUI elements attribute = CurrentComboProperty('ui.combo_attribute') level = FloatLineProperty('ui.value_level') alpha = ValueProperty('ui.slider_alpha') def __init__(self, layer_artist): super(IsosurfaceLayerStyleWidget, self).__init__() self.ui = load_ui('layer_style_widget.ui', self, directory=os.path.dirname(__file__)) self.layer_artist = layer_artist self.layer = layer_artist.layer # Set up attribute and visual options self._setup_options() self._connect_global() # Set initial values self.layer_artist.color = self.layer.style.color self.layer_artist.alpha = self.layer.style.alpha with delay_callback(self.layer_artist, 'attribute'): self.attribute = self.visible_components[0] self._update_levels() self.layer_artist.visible = True def _connect_global(self): connect_color(self.layer.style, 'color', self.ui.label_color) connect_value(self.layer.style, 'alpha', self.ui.slider_alpha, value_range=(0, 1)) def _setup_options(self): """ Set up the combo box with the list of attributes """ # Set up attribute list label_data = [(comp.label, comp) for comp in self.visible_components] update_combobox(self.ui.combo_attribute, label_data) # Set up connections with layer artist connect_current_combo(self.layer_artist, 'attribute', self.ui.combo_attribute) connect_float_edit(self.layer_artist, 'level', self.ui.value_level) connect_color(self.layer_artist, 'color', self.ui.label_color) connect_value(self.layer_artist, 'alpha', self.ui.slider_alpha, value_range=(0, 1)) # Set up internal connections self.ui.value_level.editingFinished.connect(self._cache_levels) self.ui.combo_attribute.currentIndexChanged.connect( self._update_levels) def _update_levels(self): if isinstance(self.layer, Subset): self.level = 0.5 return if not hasattr(self, '_levels'): self._levels = {} if self.attribute in self._levels: self.level = self._levels[self.attribute] else: self.level = self.default_levels(self.attribute) self._levels[self.attribute] = self.level def _cache_levels(self): if not isinstance(self.layer, Subset) or self.layer_artist.subset_mode == 'data': self._levels[self.attribute] = self.level def default_levels(self, attribute): # For subsets, we want to compute the levels based on the full # dataset not just the subset. if isinstance(self.layer, Subset): return 0.5 else: return np.nanmedian(self.layer[attribute]) @property def visible_components(self): if isinstance(self.layer, Subset): return self.layer.data.visible_components else: return self.layer.visible_components
class WWTOptionPanel(QtWidgets.QWidget): ra_att = CurrentComboDataProperty('ui.combo_ra_att') dec_att = CurrentComboDataProperty('ui.combo_dec_att') background = CurrentComboDataProperty('ui.combo_background') opacity = ValueProperty('ui.value_opacity') foreground = CurrentComboDataProperty('ui.combo_foreground') galactic_plane = ButtonProperty('ui.checkbox_galactic_plane') def __init__(self, viewer, parent=None): super(WWTOptionPanel, self).__init__(parent=parent) self.viewer = viewer self.ui = load_ui('options_widget.ui', self, directory=os.path.dirname(__file__)) self._setup_combos() self._connect() @property def ra(self): if self.ra_att is None: return None else: return self.ra_att[0] @property def dec(self): if self.dec_att is None: return None else: return self.dec_att[0] def _setup_combos(self): layers = [ 'Digitized Sky Survey (Color)', 'VLSS: VLA Low-frequency Sky Survey (Radio)', 'WMAP ILC 5-Year Cosmic Microwave Background', 'SFD Dust Map (Infrared)', 'WISE All Sky (Infrared)', 'GLIMPSE/MIPSGAL', 'Hydrogen Alpha Full Sky Map' ] labels = ['DSS', 'VLSS', 'WMAP', 'SFD', 'WISE', 'GLIMPSE', 'H Alpha'] thumbnails = [ 'DSS', 'VLA', 'wmap5yr_ilc_200uk', 'dust', 'glimpsemipsgaltn', 'halpha' ] base = ('http://www.worldwidetelescope.org/wwtweb/' 'thumbnail.aspx?name=%s') for i, row in enumerate(zip(layers, labels, thumbnails)): layer, text, thumb = row url = base % thumb data = urlopen(url).read() pm = QtGui.QPixmap() pm.loadFromData(data) icon = QtGui.QIcon(pm) self.ui.combo_foreground.addItem(icon, text, layer) self.ui.combo_foreground.setItemData(i, layer, role=Qt.ToolTipRole) self.ui.combo_background.addItem(icon, text, layer) self.ui.combo_background.setItemData(i, layer, role=Qt.ToolTipRole) self.ui.combo_foreground.setIconSize(QtCore.QSize(60, 60)) self.ui.combo_background.setIconSize(QtCore.QSize(60, 60)) self.ra_att_helper = ComponentIDComboHelper(self.ui.combo_ra_att, self.viewer._data, categorical=False, numeric=True) self.dec_att_helper = ComponentIDComboHelper(self.ui.combo_dec_att, self.viewer._data, categorical=False, numeric=True) def add_data(self, data): # TODO: the following logic should go in the component ID helpers. It # isn't quite right at the moment because if there are multiple # datasets/subsets with the same components, we only want to show those # once. if isinstance(data, Subset): self.ra_att_helper.append(data.data) self.dec_att_helper.append(data.data) else: self.ra_att_helper.append(data) self.dec_att_helper.append(data) def remove_data(self, data): if isinstance(data, Subset): self.ra_att_helper.remove(data.data) self.dec_att_helper.remove(data.data) else: self.ra_att_helper.remove(data) self.dec_att_helper.remove(data) def _connect(self): self.ui.combo_ra_att.currentIndexChanged.connect( self.viewer._update_all) self.ui.combo_dec_att.currentIndexChanged.connect( self.viewer._update_all) self.ui.combo_foreground.currentIndexChanged.connect( self.viewer._update_foreground) self.ui.combo_background.currentIndexChanged.connect( self.viewer._update_background) self.ui.value_opacity.valueChanged.connect(self.viewer._update_opacity) self.ui.checkbox_galactic_plane.toggled.connect( self.viewer._update_galactic_plane_mode) self.opacity = 100
class SliceWidget(QtGui.QWidget): label = TextProperty('_ui_label') slice_center = ValueProperty('_ui_slider.slider') mode = CurrentComboProperty('_ui_mode') slice_changed = QtCore.Signal(int) mode_changed = QtCore.Signal(str) def __init__(self, label='', pix2world=None, lo=0, hi=10, parent=None, aggregation=None): super(SliceWidget, self).__init__(parent) if aggregation is not None: raise NotImplemented("Aggregation option not implemented") if pix2world is not None: raise NotImplemented("Pix2world option not implemented") layout = QtGui.QVBoxLayout() layout.setContentsMargins(3, 1, 3, 1) layout.setSpacing(0) top = QtGui.QHBoxLayout() top.setContentsMargins(3, 3, 3, 3) label = QtGui.QLabel(label) top.addWidget(label) mode = QtGui.QComboBox() mode.addItem('x', 'x') mode.addItem('y', 'y') mode.addItem('slice', 'slice') mode.currentIndexChanged.connect(lambda x: self.mode_changed.emit(self.mode)) mode.currentIndexChanged.connect(self._update_mode) top.addWidget(mode) layout.addLayout(top) slider = load_ui('data_slice_widget.ui', None, directory=os.path.dirname(__file__)) slider.slider slider.slider.setMinimum(lo) slider.slider.setMaximum(hi) slider.slider.setValue((lo + hi) / 2) slider.slider.valueChanged.connect(lambda x: self.slice_changed.emit(self.mode)) slider.slider.valueChanged.connect(lambda x: slider.label.setText(str(x))) slider.label.setMinimumWidth(50) slider.label.setText(str(slider.slider.value())) slider.label.textChanged.connect(lambda x: slider.slider.setValue(int(x))) slider.first.clicked.connect(nonpartial(self._browse_slice, 'first')) slider.prev.clicked.connect(nonpartial(self._browse_slice, 'prev')) slider.next.clicked.connect(nonpartial(self._browse_slice, 'next')) slider.last.clicked.connect(nonpartial(self._browse_slice, 'last')) layout.addWidget(slider) self.setLayout(layout) self._ui_label = label self._ui_slider = slider self._ui_mode = mode self._update_mode() self._frozen = False def _browse_slice(self, action): imin = self._ui_slider.slider.minimum() imax = self._ui_slider.slider.maximum() value = self._ui_slider.slider.value() if action == 'first': value = imin elif action == 'last': value = imax elif action == 'prev': value = max(value - 1, imin) elif action == 'next': value = min(value + 1, imax) else: raise ValueError("Action should be one of first/prev/next/last") self._ui_slider.slider.setValue(value) def _update_mode(self, *args): if self.mode != 'slice': self._ui_slider.hide() else: self._ui_slider.show() def freeze(self): self.mode = 'slice' self._ui_mode.setEnabled(False) self._ui_slider.hide() self._frozen = True @property def frozen(self): return self._frozen
class VolumeLayerStyleWidget(QtGui.QWidget): # GUI elements attribute = CurrentComboProperty('ui.combo_attribute') vmin = FloatLineProperty('ui.value_min') vmax = FloatLineProperty('ui.value_max') alpha = ValueProperty('ui.slider_alpha') subset_outline = ButtonProperty('ui.radio_subset_outline') subset_data = ButtonProperty('ui.radio_subset_data') def __init__(self, layer_artist): super(VolumeLayerStyleWidget, self).__init__() self.ui = load_ui('layer_style_widget.ui', self, directory=os.path.dirname(__file__)) self.layer_artist = layer_artist self.layer = layer_artist.layer # Set up attribute and visual options self._setup_options() self._connect_global() # Set initial values self.layer_artist.color = self.layer.style.color self.layer_artist.alpha = self.layer.style.alpha with delay_callback(self.layer_artist, 'attribute'): self.attribute = self.visible_components[0] self._update_limits() if isinstance(self.layer, Subset): self.ui.radio_subset_data.setChecked(True) self.layer_artist.visible = True def _connect_global(self): connect_color(self.layer.style, 'color', self.ui.label_color) connect_value(self.layer.style, 'alpha', self.ui.slider_alpha, value_range=(0, 1)) def _setup_options(self): """ Set up the combo box with the list of attributes """ # Set up radio buttons for subset mode selection if this is a subset if isinstance(self.layer, Subset): self._radio_size = QtGui.QButtonGroup() self._radio_size.addButton(self.ui.radio_subset_outline) self._radio_size.addButton(self.ui.radio_subset_data) else: self.ui.radio_subset_outline.hide() self.ui.radio_subset_data.hide() self.ui.label_subset_mode.hide() # Set up attribute list label_data = [(comp.label, comp) for comp in self.visible_components] update_combobox(self.ui.combo_attribute, label_data) # Set up connections with layer artist connect_current_combo(self.layer_artist, 'attribute', self.ui.combo_attribute) connect_float_edit(self.layer_artist, 'vmin', self.ui.value_min) connect_float_edit(self.layer_artist, 'vmax', self.ui.value_max) connect_color(self.layer_artist, 'color', self.ui.label_color) connect_value(self.layer_artist, 'alpha', self.ui.slider_alpha, value_range=(0, 1)) # Set up internal connections self.ui.radio_subset_outline.toggled.connect(self._update_subset_mode) self.ui.radio_subset_data.toggled.connect(self._update_subset_mode) self.ui.value_min.editingFinished.connect(self._cache_limits) self.ui.value_max.editingFinished.connect(self._cache_limits) self.ui.combo_attribute.currentIndexChanged.connect( self._update_limits) def _update_subset_mode(self): if self.ui.radio_subset_outline.isChecked(): self.layer_artist.subset_mode = 'outline' else: self.layer_artist.subset_mode = 'data' self._update_limits() def _update_limits(self): if isinstance(self.layer, Subset): if self.layer_artist.subset_mode == 'outline': self.ui.value_min.setEnabled(False) self.ui.value_max.setEnabled(False) self.vmin, self.vmax = 0, 2 return else: self.ui.value_min.setEnabled(False) self.ui.value_max.setEnabled(True) if not hasattr(self, '_limits'): self._limits = {} if self.attribute in self._limits: self.vmin, self.vmax = self._limits[self.attribute] else: self.vmin, self.vmax = self.default_limits(self.attribute) self._limits[self.attribute] = self.vmin, self.vmax def _cache_limits(self): if not isinstance(self.layer, Subset) or self.layer_artist.subset_mode == 'data': self._limits[self.attribute] = self.vmin, self.vmax def default_limits(self, attribute): # For subsets, we want to compute the limits based on the full # dataset not just the subset. if isinstance(self.layer, Subset): vmin = 0 vmax = np.nanmax(self.layer.data[attribute]) else: vmin = np.nanmin(self.layer[attribute]) vmax = np.nanmax(self.layer[attribute]) return vmin, vmax @property def visible_components(self): if isinstance(self.layer, Subset): return self.layer.data.visible_components else: return self.layer.visible_components
class PreferencesDialog(QtGui.QDialog): theme = CurrentComboTextProperty('ui.combo_theme') background = ColorProperty('ui.color_background') foreground = ColorProperty('ui.color_foreground') data_color = ColorProperty('ui.color_default_data') data_alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1)) data_apply = ButtonProperty('ui.checkbox_apply') save_to_disk = ButtonProperty('ui.checkbox_save') def __init__(self, application, parent=None): super(PreferencesDialog, self).__init__(parent=parent) self.app = application self.ui = load_ui('preferences.ui', self, directory=os.path.dirname(__file__)) self.ui.cancel.clicked.connect(self.reject) self.ui.ok.clicked.connect(self.accept) self.ui.combo_theme.currentIndexChanged.connect( nonpartial(self._update_colors_from_theme)) from glue.config import settings self.background = settings.BACKGROUND_COLOR self.foreground = settings.FOREGROUND_COLOR self.data_color = settings.DATA_COLOR self.data_alpha = settings.DATA_ALPHA self._update_theme_from_colors() self.panes = [] from glue.config import preference_panes for label, widget_cls in sorted(preference_panes): pane = widget_cls() self.ui.tab_widget.addTab(pane, label) self.panes.append(pane) def _update_theme_from_colors(self): if (rgb(self.background) == (1, 1, 1) and rgb(self.foreground) == (0, 0, 0) and rgb(self.data_color) == (0.35, 0.35, 0.35) and np.allclose(self.data_alpha, 0.8)): self.theme = 'Black on White' elif (rgb(self.background) == (0, 0, 0) and rgb(self.foreground) == (1, 1, 1) and rgb(self.data_color) == (0.75, 0.75, 0.75) and np.allclose(self.data_alpha, 0.8)): self.theme = 'White on Black' else: self.theme = 'Custom' def _update_colors_from_theme(self): if self.theme == 'Black on White': self.foreground = 'black' self.background = 'white' self.data_color = '0.35' self.data_alpha = 0.8 elif self.theme == 'White on Black': self.foreground = 'white' self.background = 'black' self.data_color = '0.75' self.data_alpha = 0.8 elif self.theme != 'Custom': raise ValueError("Unknown theme: {0}".format(self.theme)) def accept(self): # Update default settings from glue.config import settings settings.FOREGROUND_COLOR = self.foreground settings.BACKGROUND_COLOR = self.background settings.DATA_COLOR = self.data_color settings.DATA_ALPHA = self.data_alpha for pane in self.panes: pane.finalize() # Save to disk if requested if self.save_to_disk: save_settings() # Trigger viewers to update defaults self.app._hub.broadcast( SettingsChangeMessage(self, ('FOREGROUND_COLOR', 'BACKGROUND_COLOR'))) # If requested, trigger data to update color if self.data_apply: self.app.set_data_color(settings.DATA_COLOR, settings.DATA_ALPHA) super(PreferencesDialog, self).accept()
class SliceWidget(QtGui.QWidget): label = TextProperty('_ui_label') slice_center = ValueProperty('_ui_slider.slider') mode = CurrentComboProperty('_ui_mode') slice_changed = QtCore.Signal(int) mode_changed = QtCore.Signal(str) def __init__(self, label='', pix2world=None, lo=0, hi=10, parent=None, aggregation=None): super(SliceWidget, self).__init__(parent) if aggregation is not None: raise NotImplemented("Aggregation option not implemented") if pix2world is not None: raise NotImplemented("Pix2world option not implemented") layout = QtGui.QVBoxLayout() layout.setContentsMargins(3, 1, 3, 1) layout.setSpacing(0) top = QtGui.QHBoxLayout() top.setContentsMargins(3, 3, 3, 3) label = QtGui.QLabel(label) top.addWidget(label) mode = QtGui.QComboBox() mode.addItem('x', 'x') mode.addItem('y', 'y') mode.addItem('slice', 'slice') mode.currentIndexChanged.connect( lambda x: self.mode_changed.emit(self.mode)) mode.currentIndexChanged.connect(self._update_mode) top.addWidget(mode) layout.addLayout(top) slider = load_ui('data_slice_widget.ui', None, directory=os.path.dirname(__file__)) slider.slider slider.button_first.setStyleSheet('border: 0px') slider.button_first.setIcon(get_icon('playback_first')) slider.button_prev.setStyleSheet('border: 0px') slider.button_prev.setIcon(get_icon('playback_prev')) slider.button_back.setStyleSheet('border: 0px') slider.button_back.setIcon(get_icon('playback_back')) slider.button_stop.setStyleSheet('border: 0px') slider.button_stop.setIcon(get_icon('playback_stop')) slider.button_forw.setStyleSheet('border: 0px') slider.button_forw.setIcon(get_icon('playback_forw')) slider.button_next.setStyleSheet('border: 0px') slider.button_next.setIcon(get_icon('playback_next')) slider.button_last.setStyleSheet('border: 0px') slider.button_last.setIcon(get_icon('playback_last')) slider.slider.setMinimum(lo) slider.slider.setMaximum(hi) slider.slider.setValue((lo + hi) / 2) slider.slider.valueChanged.connect( lambda x: self.slice_changed.emit(self.mode)) slider.slider.valueChanged.connect( lambda x: slider.label.setText(str(x))) slider.label.setMinimumWidth(50) slider.label.setText(str(slider.slider.value())) slider.label.textChanged.connect( lambda x: slider.slider.setValue(int(x))) self._play_timer = QtCore.QTimer() self._play_timer.setInterval(500) self._play_timer.timeout.connect(nonpartial(self._play_slice)) slider.button_first.clicked.connect( nonpartial(self._browse_slice, 'first')) slider.button_prev.clicked.connect( nonpartial(self._browse_slice, 'prev')) slider.button_back.clicked.connect( nonpartial(self._adjust_play, 'back')) slider.button_stop.clicked.connect( nonpartial(self._adjust_play, 'stop')) slider.button_forw.clicked.connect( nonpartial(self._adjust_play, 'forw')) slider.button_next.clicked.connect( nonpartial(self._browse_slice, 'next')) slider.button_last.clicked.connect( nonpartial(self._browse_slice, 'last')) layout.addWidget(slider) self.setLayout(layout) self._ui_label = label self._ui_slider = slider self._ui_mode = mode self._update_mode() self._frozen = False self._play_speed = 0 def _adjust_play(self, action): if action == 'stop': self._play_speed = 0 elif action == 'back': if self._play_speed > 0: self._play_speed = -1 else: self._play_speed -= 1 elif action == 'forw': if self._play_speed < 0: self._play_speed = +1 else: self._play_speed += 1 if self._play_speed == 0: self._play_timer.stop() else: self._play_timer.start() self._play_timer.setInterval(500 / abs(self._play_speed)) def _play_slice(self): if self._play_speed > 0: self._browse_slice('next', play=True) elif self._play_speed < 0: self._browse_slice('prev', play=True) def _browse_slice(self, action, play=False): imin = self._ui_slider.slider.minimum() imax = self._ui_slider.slider.maximum() value = self._ui_slider.slider.value() # If this was not called from _play_slice, we should stop the # animation. if not play: self._adjust_play('stop') if action == 'first': value = imin elif action == 'last': value = imax elif action == 'prev': value = value - 1 if value < imin: value = imax elif action == 'next': value = value + 1 if value > imax: value = imin else: raise ValueError("Action should be one of first/prev/next/last") self._ui_slider.slider.setValue(value) def _update_mode(self, *args): if self.mode != 'slice': self._ui_slider.hide() self._adjust_play('stop') else: self._ui_slider.show() def freeze(self): self.mode = 'slice' self._ui_mode.setEnabled(False) self._ui_slider.hide() self._frozen = True @property def frozen(self): return self._frozen
class PreferencesDialog(QtWidgets.QDialog): theme = CurrentComboTextProperty('ui.combo_theme') background = ColorProperty('ui.color_background') foreground = ColorProperty('ui.color_foreground') data_color = ColorProperty('ui.color_default_data') data_alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1)) data_apply = ButtonProperty('ui.checkbox_apply') save_to_disk = ButtonProperty('ui.checkbox_save') font_size = ValueProperty('ui.spinner_font_size') def __init__(self, application, parent=None): super(PreferencesDialog, self).__init__(parent=parent) self._app = weakref.ref(application) self.ui = load_ui('preferences.ui', self, directory=os.path.dirname(__file__)) self.ui.cancel.clicked.connect(self.reject) self.ui.ok.clicked.connect(self.accept) self.ui.combo_theme.currentIndexChanged.connect(self._update_colors_from_theme) self.ui.button_reset_dialogs.clicked.connect(self._reset_dialogs) # The following is needed because of a bug in Qt which means that # tab titles don't get scaled right. if platform.system() == 'Darwin': app = get_qapp() app_font = app.font() self.ui.tab_widget.setStyleSheet('font-size: {0}px'.format(app_font.pointSize())) from glue.config import settings self.background = settings.BACKGROUND_COLOR self.foreground = settings.FOREGROUND_COLOR self.data_color = settings.DATA_COLOR self.data_alpha = settings.DATA_ALPHA self.font_size = settings.FONT_SIZE self._update_theme_from_colors() self.panes = [] from glue.config import preference_panes for label, widget_cls in sorted(preference_panes): pane = widget_cls() self.ui.tab_widget.addTab(pane, label) self.panes.append(pane) def _update_theme_from_colors(self, *args): if (rgb(self.background) == (1, 1, 1) and rgb(self.foreground) == (0, 0, 0) and rgb(self.data_color) == (0.35, 0.35, 0.35) and np.allclose(self.data_alpha, 0.8)): self.theme = 'Black on White' elif (rgb(self.background) == (0, 0, 0) and rgb(self.foreground) == (1, 1, 1) and rgb(self.data_color) == (0.75, 0.75, 0.75) and np.allclose(self.data_alpha, 0.8)): self.theme = 'White on Black' else: self.theme = 'Custom' def _update_colors_from_theme(self, *args): if self.theme == 'Black on White': self.foreground = 'black' self.background = 'white' self.data_color = '0.35' self.data_alpha = 0.8 elif self.theme == 'White on Black': self.foreground = 'white' self.background = 'black' self.data_color = '0.75' self.data_alpha = 0.8 elif self.theme != 'Custom': raise ValueError("Unknown theme: {0}".format(self.theme)) def _reset_dialogs(self, *args): from glue.config import settings for key, _, _ in settings: if key.lower().startswith(('show_info', 'show_warn', 'show_large')): setattr(settings, key, True) def accept(self): # Update default settings from glue.config import settings settings.FOREGROUND_COLOR = self.foreground settings.BACKGROUND_COLOR = self.background settings.DATA_COLOR = self.data_color settings.DATA_ALPHA = self.data_alpha settings.FONT_SIZE = self.font_size for pane in self.panes: pane.finalize() # Save to disk if requested if self.save_to_disk: save_settings() else: settings._save_to_disk = True # Trigger viewers to update defaults app = self._app() if app is not None: app._hub.broadcast(SettingsChangeMessage(self, ('FOREGROUND_COLOR', 'BACKGROUND_COLOR', 'FONT_SIZE'))) if self.data_apply: # If requested, trigger data to update color app.set_data_color(settings.DATA_COLOR, settings.DATA_ALPHA) super(PreferencesDialog, self).accept()
class PreferencesDialog(QtWidgets.QDialog): theme = CurrentComboTextProperty('ui.combo_theme') background = ColorProperty('ui.color_background') foreground = ColorProperty('ui.color_foreground') data_color = ColorProperty('ui.color_default_data') data_alpha = ValueProperty('ui.slider_alpha', value_range=(0, 1)) data_apply = ButtonProperty('ui.checkbox_apply') show_large_data_warning = ButtonProperty( 'ui.checkbox_show_large_data_warning') individual_subset_color = ButtonProperty( 'ui.checkbox_individual_subset_color') save_to_disk = ButtonProperty('ui.checkbox_save') def __init__(self, application, parent=None): super(PreferencesDialog, self).__init__(parent=parent) self.app = application self.ui = load_ui('preferences.ui', self, directory=os.path.dirname(__file__)) self.ui.cancel.clicked.connect(self.reject) self.ui.ok.clicked.connect(self.accept) self.ui.combo_theme.currentIndexChanged.connect( nonpartial(self._update_colors_from_theme)) # The following is needed because of a bug in Qt which means that # tab titles don't get scaled right. app = get_qapp() app_font = app.font() self.ui.tab_widget.setStyleSheet('font-size: {0}px'.format( app_font.pointSize())) from glue.config import settings self.background = settings.BACKGROUND_COLOR self.foreground = settings.FOREGROUND_COLOR self.data_color = settings.DATA_COLOR self.data_alpha = settings.DATA_ALPHA self.show_large_data_warning = settings.SHOW_LARGE_DATA_WARNING self.individual_subset_color = settings.INDIVIDUAL_SUBSET_COLOR self._update_theme_from_colors() self.panes = [] from glue.config import preference_panes for label, widget_cls in sorted(preference_panes): pane = widget_cls() self.ui.tab_widget.addTab(pane, label) self.panes.append(pane) def _update_theme_from_colors(self): if (rgb(self.background) == (1, 1, 1) and rgb(self.foreground) == (0, 0, 0) and rgb(self.data_color) == (0.35, 0.35, 0.35) and np.allclose(self.data_alpha, 0.8)): self.theme = 'Black on White' elif (rgb(self.background) == (0, 0, 0) and rgb(self.foreground) == (1, 1, 1) and rgb(self.data_color) == (0.75, 0.75, 0.75) and np.allclose(self.data_alpha, 0.8)): self.theme = 'White on Black' else: self.theme = 'Custom' def _update_colors_from_theme(self): if self.theme == 'Black on White': self.foreground = 'black' self.background = 'white' self.data_color = '0.35' self.data_alpha = 0.8 elif self.theme == 'White on Black': self.foreground = 'white' self.background = 'black' self.data_color = '0.75' self.data_alpha = 0.8 elif self.theme != 'Custom': raise ValueError("Unknown theme: {0}".format(self.theme)) def accept(self): # Update default settings from glue.config import settings settings.FOREGROUND_COLOR = self.foreground settings.BACKGROUND_COLOR = self.background settings.DATA_COLOR = self.data_color settings.DATA_ALPHA = self.data_alpha settings.SHOW_LARGE_DATA_WARNING = self.show_large_data_warning settings.INDIVIDUAL_SUBSET_COLOR = self.individual_subset_color for pane in self.panes: pane.finalize() # Save to disk if requested if self.save_to_disk: save_settings() # Trigger viewers to update defaults self.app._hub.broadcast( SettingsChangeMessage(self, ('FOREGROUND_COLOR', 'BACKGROUND_COLOR'))) # If requested, trigger data to update color if self.data_apply: self.app.set_data_color(settings.DATA_COLOR, settings.DATA_ALPHA) super(PreferencesDialog, self).accept()