From 24820e952fab3566ae54e5b8fbf21610b0f0effe Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Fri, 24 Nov 2017 17:50:08 +0000 Subject: [PATCH 1/5] WIP --- .../component_manager/qt/equation_editor.py | 148 +++++++++++++- .../component_manager/qt/equation_editor.ui | 180 ++++++++++++++---- 2 files changed, 287 insertions(+), 41 deletions(-) diff --git a/glue/dialogs/component_manager/qt/equation_editor.py b/glue/dialogs/component_manager/qt/equation_editor.py index 1b99ea9b3..ebb819b36 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.py +++ b/glue/dialogs/component_manager/qt/equation_editor.py @@ -3,11 +3,17 @@ import os from collections import deque, OrderedDict +try: + from inspect import getfullargspec +except ImportError: # Python 2.7 + from inspect import getargspec as getfullargspec + from qtpy import QtWidgets, QtCore from qtpy.QtCore import Qt +from glue.config import link_function, link_helper from glue.core.parse import InvalidTagError, ParsedCommand, TAG_RE -from glue.utils.qt import load_ui, CompletionTextEdit +from glue.utils.qt import load_ui, CompletionTextEdit, update_combobox, fix_tab_widget_fontsize __all__ = ['EquationEditorDialog'] @@ -88,6 +94,34 @@ def format_components(m): self._cache = self.toPlainText() +def get_function_name(item): + if hasattr(item, 'display') and item.display is not None: + return item.display + else: + return item.__name__ + + +def function_label(function): + """ Provide a label for a function + + :param function: A member from the glue.config.link_function registry + """ + args = getfullargspec(function.function)[0] + args = ', '.join(args) + output = function.output_labels + output = ', '.join(output) + label = "Link from %s to %s" % (args, output) + return label + + +def helper_label(helper): + """ Provide a label for a link helper + + :param helper: A member from the glue.config.link_helper registry + """ + return helper.info + + class EquationEditorDialog(QtWidgets.QDialog): def __init__(self, data=None, equation=None, references=None, parent=None): @@ -97,6 +131,103 @@ def __init__(self, data=None, equation=None, references=None, parent=None): self.ui = load_ui('equation_editor.ui', self, directory=os.path.dirname(__file__)) + fix_tab_widget_fontsize(self.ui.tab) + + self._setup_freeform_tab(data=data, equation=equation, references=references) + self._setup_predefined_tab(data=data) + + def _setup_predefined_tab(self, data=None): + + # Populate category combo + f = [f for f in link_function.members if len(f.output_labels) == 1] + categories = sorted(set(l.category for l in f + link_helper.members)) + for category in categories: + self.ui.combosel_category.addItem(category) + self.ui.combosel_category.setCurrentIndex(0) + self.ui.combosel_category.currentIndexChanged.connect(self._populate_function_combo) + self._populate_function_combo() + + self.ui.combosel_function.setCurrentIndex(0) + self.ui.combosel_function.currentIndexChanged.connect(self._setup_inputs) + self._setup_inputs() + + @property + def category(self): + return self.ui.combosel_category.currentText() + + @property + def function(self): + return self.ui.combosel_function.currentData() + + @property + def is_helper(self): + return self.function is not None and type(self.function).__name__ == 'LinkHelper' + + @property + def is_function(self): + return self.function is not None and type(self.function).__name__ == 'LinkFunction' + + def _setup_inputs(self, event=None): + if self.is_function: + self._setup_inputs_function() + else: + self._setup_inputs_helper() + + def _clear_inputs_layout(self): + while self.ui.layout_inputs.count() > 0: + item = self.ui.layout_inputs.itemAt(0) + self.ui.layout_inputs.removeItem(item) + item.widget().setParent(None) + + def _add_input_widget(self, name): + widget = QtWidgets.QWidget() + layout = QtWidgets.QHBoxLayout() + label = QtWidgets.QLabel(name) + combo = QtWidgets.QComboBox() + update_combobox(combo, list(self.references.items())) + layout.addWidget(label) + layout.addWidget(combo) + widget.setLayout(layout) + layout.setContentsMargins(1, 0, 1, 1) + self.ui.layout_inputs.addWidget(widget) + + def _setup_inputs_function(self): + + func = self.function.function + args = getfullargspec(func)[0] + label = function_label(self.function) + self.ui.label_info.setText(label) + + self._clear_inputs_layout() + + for a in args: + self._add_input_widget(a) + + def _setup_inputs_helper(self): + + # Here it looks like helpers need to be clearer which are the inputs from + # one side and the other side (input/output) + + label = helper_label(self.function) + args = self.function.input_labels + self.ui.label_info.setText(label) + + self._clear_inputs_layout() + + for a in args: + self._add_input_widget(a) + + def _populate_function_combo(self, event=None): + """ + Add name of functions to function combo box + """ + f = [f for f in link_function.members if len(f.output_labels) == 1] + functions = ((get_function_name(l[0]), l) for l in f + link_helper.members if l.category == self.category) + update_combobox(self.ui.combosel_function, functions) + self._setup_inputs() + + def _setup_freeform_tab(self, data=None, equation=None, references=None): + self.equation = equation # Get mapping from label to component ID @@ -141,7 +272,7 @@ def _update_status(self): else: try: pc = self._get_parsed_command() - pc.evaluate_test() + result = pc.evaluate_test() except SyntaxError: self.ui.label_status.setStyleSheet('color: red') self.ui.label_status.setText("Incomplete or invalid syntax") @@ -155,9 +286,14 @@ def _update_status(self): self.ui.label_status.setText(str(exc)) self.ui.button_ok.setEnabled(False) else: - self.ui.label_status.setStyleSheet('color: green') - self.ui.label_status.setText("Valid expression") - self.ui.button_ok.setEnabled(True) + if result is None: + self.ui.label_status.setStyleSheet('color: red') + self.ui.label_status.setText("Expression should not return None") + self.ui.button_ok.setEnabled(False) + else: + self.ui.label_status.setStyleSheet('color: green') + self.ui.label_status.setText("Valid expression") + self.ui.button_ok.setEnabled(True) self._cache = self._get_raw_command() @@ -179,9 +315,11 @@ def reject(self): if __name__ == "__main__": # pragma: nocover + from glue.main import load_plugins from glue.utils.qt import get_qapp app = get_qapp() + load_plugins() from glue.core.data import Data d = Data(label='test1', x=[1, 2, 3], y=[2, 3, 4], z=[3, 4, 5]) diff --git a/glue/dialogs/component_manager/qt/equation_editor.ui b/glue/dialogs/component_manager/qt/equation_editor.ui index e4ae41a20..936911ec6 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.ui +++ b/glue/dialogs/component_manager/qt/equation_editor.ui @@ -6,8 +6,8 @@ 0 0 - 384 - 256 + 748 + 493 @@ -30,12 +30,9 @@ 5 - + - Derived components are components that are evaluated on-the-fly based on other components. In the box below, you can compose mathematical expressions that include components from the data. To add a component, select it in the dropdown list and click on 'Insert'. You can also type component names directly, provided that you surround them by curly braces, e.g. {x}. A status message below indicates whether the expression is valid. You can use any variable defined inside your config.py file, as well as numpy.<function>, np.<function> and math.<function> (e.g. np.log10 or math.sqrt). Once you click 'OK', you will be able to name the new component. - - - Qt::AlignJustify|Qt::AlignVCenter + Derived components are components that are evaluated on-the-fly based on other components. You can either define these using free-form expressions, or existing pre-defined transformations. true @@ -43,39 +40,150 @@ - - - - - - - - - 100 - 16777215 - - - - Insert - - - - - - - - - - 0 - 0 - + + + Qt::Vertical - + + QSizePolicy::Fixed + + - 16777215 - 16777215 + 10 + 5 + + + + + + 0 + + + + Pre-defined transformations + + + + 5 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + + + + + + + + + + Info + + + + + + + The function above takes the following inputs - select a component for each output: + + + + + + + + + + Description of which components this will create + + + + + + + + Free-from expression + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + In the box below, you can compose mathematical expressions that include components from the data. To add a component, select it in the dropdown list and click on 'Insert'. You can also type component names directly, provided that you surround them by curly braces, e.g. {x}. A status message below indicates whether the expression is valid. You can use any variable defined inside your config.py file, as well as numpy.<function>, np.<function> and math.<function> (e.g. np.log10 or math.sqrt). Once you click 'OK', you will be able to name the new component. + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + + + + + + + + + + + 100 + 16777215 + + + + Insert + + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + + From d330baf4f0863693f4e8068b8b4a1100d1476ce6 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Mon, 27 Nov 2017 08:55:33 +0000 Subject: [PATCH 2/5] Split labels for multi-links into input/output labels --- glue/config.py | 18 +++++----- glue/core/link_helpers.py | 15 +++++---- .../coordinate_helpers/link_helpers.py | 33 +++++++++++-------- .../tests/test_link_helpers.py | 2 +- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/glue/config.py b/glue/config.py index 2d4c20066..328162777 100644 --- a/glue/config.py +++ b/glue/config.py @@ -615,11 +615,11 @@ class LinkHelperRegistry(Registry): """Stores helper objects that compute many ComponentLinks at once - The members property is a list of (object, info_string, - input_labels) tuples. `Object` is the link helper. `info_string` - describes what `object` does. `input_labels` is a list labeling - the inputs. ``category`` is a category in which the link funtion will appear - (defaults to 'General'). + The members property is a list of (object, info_string, input_labels, + output_labels) tuples. `Object` is the link helper. `info_string` describes + what `object` does. `input_labels` is a list labeling the inputs, and + `output_labels` is a list labeling the outputs. ``category`` is a category + in which the link funtion will appear (defaults to 'General'). Each link helper takes a list of ComponentIDs as inputs, and returns an iterable object (e.g. list) of ComponentLinks. @@ -627,16 +627,16 @@ class LinkHelperRegistry(Registry): New helpers can be registered via @link_helper('Links degrees and arcseconds in both directions', - ['degree', 'arcsecond']) + input_labels=['degree'], output_labels=['arcsecond']) def new_helper(degree, arcsecond): return [ComponentLink([degree], arcsecond, using=lambda d: d*3600), ComponentLink([arcsecond], degree, using=lambda a: a/3600)] """ - item = namedtuple('LinkHelper', 'helper info input_labels category') + item = namedtuple('LinkHelper', 'helper info input_labels output_labels category') - def __call__(self, info, input_labels, category='General'): + def __call__(self, info, input_labels, output_labels, category='General'): def adder(func): - self.add(self.item(func, info, input_labels, category)) + self.add(self.item(func, info, input_labels, output_labels, category)) return func return adder diff --git a/glue/core/link_helpers.py b/glue/core/link_helpers.py index 0365e7f05..38f3c0427 100644 --- a/glue/core/link_helpers.py +++ b/glue/core/link_helpers.py @@ -112,12 +112,13 @@ class MultiLink(LinkCollection): cids = None - def __init__(self, *args): - self.cids = args + def __init__(self, cids_left, cids_right): + self.cids_left = cids_left + self.cids_right = cids_right def create_links(self, cids_left, cids_right, forwards=None, backwards=None): - if self.cids is None: + if self.cids_left is None or self.cids_right is None: raise Exception("MultiLink.__init__ was not called before creating links") if forwards is None and backwards is None: @@ -134,15 +135,17 @@ def create_links(self, cids_left, cids_right, forwards=None, backwards=None): self.append(ComponentLink(cids_right, l, func)) def __gluestate__(self, context): - return {'cids': [context.id(cid) for cid in self.cids]} + return {'cids_left': [context.id(cid) for cid in self.cids_left], + 'cids_right': [context.id(cid) for cid in self.cids_right]} @classmethod def __setgluestate__(cls, rec, context): - return cls(*[context.object(cid) for cid in rec['cids']]) + return cls([context.object(cid) for cid in rec['cids_left']], + [context.object(cid) for cid in rec['cids_right']]) def multi_link(cids_left, cids_right, forwards=None, backwards=None): - ml = MultiLink(cids_left + cids_right) + ml = MultiLink(cids_left, cids_right) ml.create_links(cids_left, cids_right, forwards=forwards, backwards=backwards) return ml diff --git a/glue/plugins/coordinate_helpers/link_helpers.py b/glue/plugins/coordinate_helpers/link_helpers.py index 7afeb82f3..7cdbe9d0e 100644 --- a/glue/plugins/coordinate_helpers/link_helpers.py +++ b/glue/plugins/coordinate_helpers/link_helpers.py @@ -23,9 +23,9 @@ class BaseCelestialMultiLink(MultiLink): frame_in = None frame_out = None - def __init__(self, in_lon, in_lat, out_lon, out_lat): - super(BaseCelestialMultiLink, self).__init__(in_lon, in_lat, out_lon, out_lat) - self.create_links([in_lon, in_lat], [out_lon, out_lat], + def __init__(self, cids_left, cids_right): + super(BaseCelestialMultiLink, self).__init__(cids_left, cids_right) + self.create_links(cids_left, cids_right, forwards=self.forward, backwards=self.backward) def forward(self, in_lon, in_lat): @@ -40,7 +40,8 @@ def backward(self, in_lon, in_lat): @link_helper('Link Galactic and FK5 (J2000) Equatorial coordinates', - input_labels=['l', 'b', 'ra (fk5)', 'dec (fk5)'], + input_labels=['l', 'b'], + output_labels=['ra (fk5)', 'dec (fk5)'], category='Astronomy') class Galactic_to_FK5(BaseCelestialMultiLink): display = "Galactic <-> FK5 (J2000)" @@ -49,7 +50,8 @@ class Galactic_to_FK5(BaseCelestialMultiLink): @link_helper('Link FK4 (B1950) and FK5 (J2000) Equatorial coordinates', - input_labels=['ra (fk4)', 'dec (fk4)', 'ra (fk5)', 'dec (fk5)'], + input_labels=['ra (fk4)', 'dec (fk4)'], + output_labels=['ra (fk5)', 'dec (fk5)'], category='Astronomy') class FK4_to_FK5(BaseCelestialMultiLink): display = "FK4 (B1950) <-> FK5 (J2000)" @@ -58,7 +60,8 @@ class FK4_to_FK5(BaseCelestialMultiLink): @link_helper('Link ICRS and FK5 (J2000) Equatorial coordinates', - input_labels=['ra (icrs)', 'dec (icrs)', 'ra (fk5)', 'dec (fk5)'], + input_labels=['ra (icrs)', 'dec (icrs)'], + output_labels=['ra (fk5)', 'dec (fk5)'], category='Astronomy') class ICRS_to_FK5(BaseCelestialMultiLink): display = "ICRS <-> FK5 (J2000)" @@ -67,7 +70,8 @@ class ICRS_to_FK5(BaseCelestialMultiLink): @link_helper('Link Galactic and FK4 (B1950) Equatorial coordinates', - input_labels=['l', 'b', 'ra (fk4)', 'dec (fk4)'], + input_labels=['l', 'b'], + output_labels=['ra (fk4)', 'dec (fk4)'], category='Astronomy') class Galactic_to_FK4(BaseCelestialMultiLink): display = "Galactic <-> FK4 (B1950)" @@ -76,7 +80,8 @@ class Galactic_to_FK4(BaseCelestialMultiLink): @link_helper('Link ICRS and FK4 (B1950) Equatorial coordinates', - input_labels=['ra (icrs)', 'dec (icrs)', 'ra (fk4)', 'dec (fk4)'], + input_labels=['ra (icrs)', 'dec (icrs)'], + output_labels=['ra (fk4)', 'dec (fk4)'], category='Astronomy') class ICRS_to_FK4(BaseCelestialMultiLink): display = "ICRS <-> FK4 (B1950)" @@ -85,7 +90,8 @@ class ICRS_to_FK4(BaseCelestialMultiLink): @link_helper('Link ICRS and Galactic coordinates', - input_labels=['ra (icrs)', 'dec (icrs)', 'l', 'b'], + input_labels=['ra (icrs)', 'dec (icrs)'], + output_labels=['l', 'b'], category='Astronomy') class ICRS_to_Galactic(BaseCelestialMultiLink): display = "ICRS <-> Galactic" @@ -94,15 +100,16 @@ class ICRS_to_Galactic(BaseCelestialMultiLink): @link_helper('Link 3D Galactocentric and Galactic coordinates', - input_labels=['x (kpc)', 'y (kpc)', 'z (kpc)', 'l (deg)', 'b (deg)', 'distance (kpc)'], + input_labels=['x (kpc)', 'y (kpc)', 'z (kpc)'], + output_labels=['l (deg)', 'b (deg)', 'distance (kpc)'], category='Astronomy') class GalactocentricToGalactic(MultiLink): display = "3D Galactocentric <-> Galactic" - def __init__(self, x_id, y_id, z_id, l_id, b_id, d_id): - super(GalactocentricToGalactic, self).__init__(x_id, y_id, z_id, l_id, b_id, d_id) - self.create_links([x_id, y_id, z_id], [l_id, b_id, d_id], + def __init__(self, cids_left, cids_right): + super(GalactocentricToGalactic, self).__init__(cids_left, cids_right) + self.create_links(cids_left, cids_right, self.forward, self.backward) def forward(self, x_kpc, y_kpc, z_kpc): diff --git a/glue/plugins/coordinate_helpers/tests/test_link_helpers.py b/glue/plugins/coordinate_helpers/tests/test_link_helpers.py index 145ffccc3..324f73b16 100644 --- a/glue/plugins/coordinate_helpers/tests/test_link_helpers.py +++ b/glue/plugins/coordinate_helpers/tests/test_link_helpers.py @@ -32,7 +32,7 @@ @pytest.mark.parametrize(('conv_class', 'expected'), list(EXPECTED.items())) def test_conversion(conv_class, expected): - result = conv_class(lon1, lat1, lon2, lat2) + result = conv_class([lon1, lat1], [lon2, lat2]) assert len(result) == 4 From 51870a6106025ffa18fb30d22a28a2be2362f0dc Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Wed, 29 Nov 2017 13:51:30 +0000 Subject: [PATCH 3/5] More improvements to derived component editor --- .../component_manager/qt/equation_editor.py | 160 +++++++++++------- .../component_manager/qt/equation_editor.ui | 95 +++++++++-- 2 files changed, 180 insertions(+), 75 deletions(-) diff --git a/glue/dialogs/component_manager/qt/equation_editor.py b/glue/dialogs/component_manager/qt/equation_editor.py index ebb819b36..84fac941b 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.py +++ b/glue/dialogs/component_manager/qt/equation_editor.py @@ -136,6 +136,8 @@ def __init__(self, data=None, equation=None, references=None, parent=None): self._setup_freeform_tab(data=data, equation=equation, references=references) self._setup_predefined_tab(data=data) + self.ui.tab.currentChanged.connect(self._update_status) + def _setup_predefined_tab(self, data=None): # Populate category combo @@ -168,54 +170,78 @@ def is_function(self): return self.function is not None and type(self.function).__name__ == 'LinkFunction' def _setup_inputs(self, event=None): + if self.is_function: - self._setup_inputs_function() + label = function_label(self.function) + input_labels = getfullargspec(self.function.function)[0] + else: - self._setup_inputs_helper() + label = helper_label(self.function) + input_labels = self.function.input_labels - def _clear_inputs_layout(self): - while self.ui.layout_inputs.count() > 0: - item = self.ui.layout_inputs.itemAt(0) - self.ui.layout_inputs.removeItem(item) - item.widget().setParent(None) + self.ui.label_info.setText(label) - def _add_input_widget(self, name): - widget = QtWidgets.QWidget() - layout = QtWidgets.QHBoxLayout() - label = QtWidgets.QLabel(name) - combo = QtWidgets.QComboBox() - update_combobox(combo, list(self.references.items())) - layout.addWidget(label) - layout.addWidget(combo) - widget.setLayout(layout) - layout.setContentsMargins(1, 0, 1, 1) - self.ui.layout_inputs.addWidget(widget) + self._clear_input_output_layouts() - def _setup_inputs_function(self): + input_message = "The function above takes the following input(s):" - func = self.function.function - args = getfullargspec(func)[0] - label = function_label(self.function) - self.ui.label_info.setText(label) + if len(input_labels) > 1: + input_message = input_message.replace('(s)', 's') + else: + input_message = input_message.replace('(s)', '') - self._clear_inputs_layout() + self.ui.layout_inout.addWidget(QtWidgets.QLabel(input_message), 0, 1, 1, 3) - for a in args: - self._add_input_widget(a) + spacer1 = QtWidgets.QSpacerItem(10, 5, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Fixed) + spacer2 = QtWidgets.QSpacerItem(10, 5, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Fixed) + self.ui.layout_inout.addItem(spacer1, 0, 0) + self.ui.layout_inout.addItem(spacer2, 0, 4) - def _setup_inputs_helper(self): + row = 0 + for a in input_labels: + row += 1 + self._add_input_widget(a, row) - # Here it looks like helpers need to be clearer which are the inputs from - # one side and the other side (input/output) + output_message = "This function produces the following output(s) - you can set the label(s) here:" - label = helper_label(self.function) - args = self.function.input_labels - self.ui.label_info.setText(label) + if len(self.function.output_labels) > 1: + output_message = output_message.replace('(s)', 's') + else: + output_message = output_message.replace('(s)', '') + + row += 1 + self.ui.layout_inout.addWidget(QtWidgets.QLabel(output_message), row, 1, 1, 3) + + for a in self.function.output_labels: + row += 1 + self._add_output_widget(a, row) + + def _clear_input_output_layouts(self): + + for row in range(self.ui.layout_inout.rowCount()): + for col in range(self.ui.layout_inout.columnCount()): + item = self.ui.layout_inout.itemAtPosition(row, col) + if item is not None: + self.ui.layout_inout.removeItem(item) + if item.widget() is not None: + item.widget().setParent(None) - self._clear_inputs_layout() + def _add_input_widget(self, name, row): + label = QtWidgets.QLabel(name) + combo = QtWidgets.QComboBox() + update_combobox(combo, list(self.references.items())) + self.ui.layout_inout.addWidget(label, row, 1) + self.ui.layout_inout.addWidget(combo, row, 2) - for a in args: - self._add_input_widget(a) + def _add_output_widget(self, name, row): + label = QtWidgets.QLabel(name) + edit = QtWidgets.QLineEdit() + self.ui.layout_inout.addWidget(label, row, 1) + self.ui.layout_inout.addWidget(edit, row, 2) def _populate_function_combo(self, event=None): """ @@ -260,42 +286,50 @@ def _insert_component(self): label = self.ui.combosel_component.currentText() self.expression.insertPlainText('{' + label + '}') - def _update_status(self): + def _update_status(self, event=None): - # If the text hasn't changed, no need to check again - if hasattr(self, '_cache') and self._get_raw_command() == self._cache: - return + if self.ui.tab.currentIndex() == 0: + + self.ui.label_status.setStyleSheet('color: green') + self.ui.label_status.setText('') + self.ui.button_ok.setEnabled(True) - if self._get_raw_command() == "": - self.ui.label_status.setText("") - self.ui.button_ok.setEnabled(False) else: - try: - pc = self._get_parsed_command() - result = pc.evaluate_test() - except SyntaxError: - self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText("Incomplete or invalid syntax") - self.ui.button_ok.setEnabled(False) - except InvalidTagError as exc: - self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText("Invalid component: {0}".format(exc.tag)) - self.ui.button_ok.setEnabled(False) - except Exception as exc: - self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText(str(exc)) + + # If the text hasn't changed, no need to check again + if hasattr(self, '_cache') and self._get_raw_command() == self._cache and event is None: + return + + if self._get_raw_command() == "": + self.ui.label_status.setText("") self.ui.button_ok.setEnabled(False) else: - if result is None: + try: + pc = self._get_parsed_command() + result = pc.evaluate_test() + except SyntaxError: + self.ui.label_status.setStyleSheet('color: red') + self.ui.label_status.setText("Incomplete or invalid syntax") + self.ui.button_ok.setEnabled(False) + except InvalidTagError as exc: self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText("Expression should not return None") + self.ui.label_status.setText("Invalid component: {0}".format(exc.tag)) + self.ui.button_ok.setEnabled(False) + except Exception as exc: + self.ui.label_status.setStyleSheet('color: red') + self.ui.label_status.setText(str(exc)) self.ui.button_ok.setEnabled(False) else: - self.ui.label_status.setStyleSheet('color: green') - self.ui.label_status.setText("Valid expression") - self.ui.button_ok.setEnabled(True) - - self._cache = self._get_raw_command() + if result is None: + self.ui.label_status.setStyleSheet('color: red') + self.ui.label_status.setText("Expression should not return None") + self.ui.button_ok.setEnabled(False) + else: + self.ui.label_status.setStyleSheet('color: green') + self.ui.label_status.setText("Valid expression") + self.ui.button_ok.setEnabled(True) + + self._cache = self._get_raw_command() def _get_raw_command(self): return str(self.ui.expression.toPlainText()) diff --git a/glue/dialogs/component_manager/qt/equation_editor.ui b/glue/dialogs/component_manager/qt/equation_editor.ui index 936911ec6..f043775a6 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.ui +++ b/glue/dialogs/component_manager/qt/equation_editor.ui @@ -6,8 +6,8 @@ 0 0 - 748 - 493 + 532 + 402 @@ -17,6 +17,9 @@ 5 + + QLayout::SetDefaultConstraint + 10 @@ -90,29 +93,63 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + 75 + true + + Info + + Qt::AlignCenter + - - - The function above takes the following inputs - select a component for each output: + + + Qt::Vertical - + + + 20 + 40 + + + - + - - - Description of which components this will create + + + Qt::Vertical - + + + 20 + 40 + + + @@ -169,7 +206,7 @@ - + 0 0 @@ -182,6 +219,40 @@ + + + + + + Derived component name: + + + + + + + + 100 + 16777215 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + From 75369aa9171a06996e12a7f6c59160cbc1a84f1b Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Wed, 29 Nov 2017 14:18:15 +0000 Subject: [PATCH 4/5] Split code into equation editor and functional derived component editor --- .../component_manager/qt/equation_editor.py | 264 ++-------------- .../component_manager/qt/equation_editor.ui | 295 ++++-------------- .../component_manager/qt/function_editor.py | 188 +++++++++++ .../component_manager/qt/function_editor.ui | 111 +++++++ 4 files changed, 384 insertions(+), 474 deletions(-) create mode 100644 glue/dialogs/component_manager/qt/function_editor.py create mode 100644 glue/dialogs/component_manager/qt/function_editor.ui diff --git a/glue/dialogs/component_manager/qt/equation_editor.py b/glue/dialogs/component_manager/qt/equation_editor.py index 84fac941b..89acd56c3 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.py +++ b/glue/dialogs/component_manager/qt/equation_editor.py @@ -1,19 +1,14 @@ from __future__ import absolute_import, division, print_function import os -from collections import deque, OrderedDict - -try: - from inspect import getfullargspec -except ImportError: # Python 2.7 - from inspect import getargspec as getfullargspec +from collections import deque from qtpy import QtWidgets, QtCore from qtpy.QtCore import Qt -from glue.config import link_function, link_helper +from glue.external.echo import CallbackProperty from glue.core.parse import InvalidTagError, ParsedCommand, TAG_RE -from glue.utils.qt import load_ui, CompletionTextEdit, update_combobox, fix_tab_widget_fontsize +from glue.utils.qt import load_ui, CompletionTextEdit __all__ = ['EquationEditorDialog'] @@ -94,176 +89,18 @@ def format_components(m): self._cache = self.toPlainText() -def get_function_name(item): - if hasattr(item, 'display') and item.display is not None: - return item.display - else: - return item.__name__ - - -def function_label(function): - """ Provide a label for a function - - :param function: A member from the glue.config.link_function registry - """ - args = getfullargspec(function.function)[0] - args = ', '.join(args) - output = function.output_labels - output = ', '.join(output) - label = "Link from %s to %s" % (args, output) - return label - - -def helper_label(helper): - """ Provide a label for a link helper - - :param helper: A member from the glue.config.link_helper registry - """ - return helper.info - - class EquationEditorDialog(QtWidgets.QDialog): - def __init__(self, data=None, equation=None, references=None, parent=None): + valid = CallbackProperty('') + message = CallbackProperty('') + + def __init__(self, equation=None, references=None, parent=None): super(EquationEditorDialog, self).__init__(parent=parent) self.ui = load_ui('equation_editor.ui', self, directory=os.path.dirname(__file__)) - fix_tab_widget_fontsize(self.ui.tab) - - self._setup_freeform_tab(data=data, equation=equation, references=references) - self._setup_predefined_tab(data=data) - - self.ui.tab.currentChanged.connect(self._update_status) - - def _setup_predefined_tab(self, data=None): - - # Populate category combo - f = [f for f in link_function.members if len(f.output_labels) == 1] - categories = sorted(set(l.category for l in f + link_helper.members)) - for category in categories: - self.ui.combosel_category.addItem(category) - self.ui.combosel_category.setCurrentIndex(0) - self.ui.combosel_category.currentIndexChanged.connect(self._populate_function_combo) - self._populate_function_combo() - - self.ui.combosel_function.setCurrentIndex(0) - self.ui.combosel_function.currentIndexChanged.connect(self._setup_inputs) - self._setup_inputs() - - @property - def category(self): - return self.ui.combosel_category.currentText() - - @property - def function(self): - return self.ui.combosel_function.currentData() - - @property - def is_helper(self): - return self.function is not None and type(self.function).__name__ == 'LinkHelper' - - @property - def is_function(self): - return self.function is not None and type(self.function).__name__ == 'LinkFunction' - - def _setup_inputs(self, event=None): - - if self.is_function: - label = function_label(self.function) - input_labels = getfullargspec(self.function.function)[0] - - else: - label = helper_label(self.function) - input_labels = self.function.input_labels - - self.ui.label_info.setText(label) - - self._clear_input_output_layouts() - - input_message = "The function above takes the following input(s):" - - if len(input_labels) > 1: - input_message = input_message.replace('(s)', 's') - else: - input_message = input_message.replace('(s)', '') - - self.ui.layout_inout.addWidget(QtWidgets.QLabel(input_message), 0, 1, 1, 3) - - spacer1 = QtWidgets.QSpacerItem(10, 5, - QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Fixed) - spacer2 = QtWidgets.QSpacerItem(10, 5, - QtWidgets.QSizePolicy.Expanding, - QtWidgets.QSizePolicy.Fixed) - self.ui.layout_inout.addItem(spacer1, 0, 0) - self.ui.layout_inout.addItem(spacer2, 0, 4) - - row = 0 - for a in input_labels: - row += 1 - self._add_input_widget(a, row) - - output_message = "This function produces the following output(s) - you can set the label(s) here:" - - if len(self.function.output_labels) > 1: - output_message = output_message.replace('(s)', 's') - else: - output_message = output_message.replace('(s)', '') - - row += 1 - self.ui.layout_inout.addWidget(QtWidgets.QLabel(output_message), row, 1, 1, 3) - - for a in self.function.output_labels: - row += 1 - self._add_output_widget(a, row) - - def _clear_input_output_layouts(self): - - for row in range(self.ui.layout_inout.rowCount()): - for col in range(self.ui.layout_inout.columnCount()): - item = self.ui.layout_inout.itemAtPosition(row, col) - if item is not None: - self.ui.layout_inout.removeItem(item) - if item.widget() is not None: - item.widget().setParent(None) - - def _add_input_widget(self, name, row): - label = QtWidgets.QLabel(name) - combo = QtWidgets.QComboBox() - update_combobox(combo, list(self.references.items())) - self.ui.layout_inout.addWidget(label, row, 1) - self.ui.layout_inout.addWidget(combo, row, 2) - - def _add_output_widget(self, name, row): - label = QtWidgets.QLabel(name) - edit = QtWidgets.QLineEdit() - self.ui.layout_inout.addWidget(label, row, 1) - self.ui.layout_inout.addWidget(edit, row, 2) - - def _populate_function_combo(self, event=None): - """ - Add name of functions to function combo box - """ - f = [f for f in link_function.members if len(f.output_labels) == 1] - functions = ((get_function_name(l[0]), l) for l in f + link_helper.members if l.category == self.category) - update_combobox(self.ui.combosel_function, functions) - self._setup_inputs() - - def _setup_freeform_tab(self, data=None, equation=None, references=None): - - self.equation = equation - - # Get mapping from label to component ID - if references is not None: - self.references = references - elif data is not None: - self.references = OrderedDict() - for cid in data.primary_components: - self.references[cid.label] = cid - # Populate component combo for label, cid in self.references.items(): self.ui.combosel_component.addItem(label, userData=cid) @@ -272,11 +109,9 @@ def _setup_freeform_tab(self, data=None, equation=None, references=None): labels = ['{' + label + '}' for label in self.references] self.ui.expression.set_word_list(labels) + # Set initial equation self.ui.expression.insertPlainText(equation) - self.ui.button_ok.clicked.connect(self.accept) - self.ui.button_cancel.clicked.connect(self.reject) - self.ui.button_insert.clicked.connect(self._insert_component) self.ui.expression.updated.connect(self._update_status) @@ -288,48 +123,35 @@ def _insert_component(self): def _update_status(self, event=None): - if self.ui.tab.currentIndex() == 0: - - self.ui.label_status.setStyleSheet('color: green') - self.ui.label_status.setText('') - self.ui.button_ok.setEnabled(True) + # If the text hasn't changed, no need to check again + if hasattr(self, '_cache') and self._get_raw_command() == self._cache and event is None: + return + if self._get_raw_command() == "": + self.message = "" + self.valid = True else: - - # If the text hasn't changed, no need to check again - if hasattr(self, '_cache') and self._get_raw_command() == self._cache and event is None: - return - - if self._get_raw_command() == "": - self.ui.label_status.setText("") - self.ui.button_ok.setEnabled(False) + try: + pc = self._get_parsed_command() + result = pc.evaluate_test() + except SyntaxError: + self.valid = False + self.message = "Incomplete or invalid syntax" + except InvalidTagError as exc: + self.valid = False + self.message = "Invalid component: {0}".format(exc.tag) + except Exception as exc: + self.valid = False + self.message = str(exc) else: - try: - pc = self._get_parsed_command() - result = pc.evaluate_test() - except SyntaxError: - self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText("Incomplete or invalid syntax") - self.ui.button_ok.setEnabled(False) - except InvalidTagError as exc: - self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText("Invalid component: {0}".format(exc.tag)) - self.ui.button_ok.setEnabled(False) - except Exception as exc: - self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText(str(exc)) - self.ui.button_ok.setEnabled(False) + if result is None: + self.valid = False + self.message = "Expression should not return None" else: - if result is None: - self.ui.label_status.setStyleSheet('color: red') - self.ui.label_status.setText("Expression should not return None") - self.ui.button_ok.setEnabled(False) - else: - self.ui.label_status.setStyleSheet('color: green') - self.ui.label_status.setText("Valid expression") - self.ui.button_ok.setEnabled(True) + self.valid = True + self.message = "Valid expression" - self._cache = self._get_raw_command() + self._cache = self._get_raw_command() def _get_raw_command(self): return str(self.ui.expression.toPlainText()) @@ -337,25 +159,3 @@ def _get_raw_command(self): def _get_parsed_command(self): expression = self._get_raw_command() return ParsedCommand(expression, self.references) - - def accept(self): - self.final_expression = self._get_parsed_command()._cmd - super(EquationEditorDialog, self).accept() - - def reject(self): - self.final_expression = None - super(EquationEditorDialog, self).reject() - - -if __name__ == "__main__": # pragma: nocover - - from glue.main import load_plugins - from glue.utils.qt import get_qapp - - app = get_qapp() - load_plugins() - - from glue.core.data import Data - d = Data(label='test1', x=[1, 2, 3], y=[2, 3, 4], z=[3, 4, 5]) - widget = EquationEditorDialog(d, '') - widget.exec_() diff --git a/glue/dialogs/component_manager/qt/equation_editor.ui b/glue/dialogs/component_manager/qt/equation_editor.ui index f043775a6..765740b5a 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.ui +++ b/glue/dialogs/component_manager/qt/equation_editor.ui @@ -6,36 +6,33 @@ 0 0 - 532 - 402 + 501 + 398 Derived Component Editor - - - 5 - - - QLayout::SetDefaultConstraint - + 10 - 5 + 10 10 - 5 + 10 - + - Derived components are components that are evaluated on-the-fly based on other components. You can either define these using free-form expressions, or existing pre-defined transformations. + In the box below, you can compose mathematical expressions that include components from the data. To add a component, select it in the dropdown list and click on 'Insert'. You can also type component names directly, provided that you surround them by curly braces, e.g. {x}. A status message below indicates whether the expression is valid. You can use any variable defined inside your config.py file, as well as numpy.<function>, np.<function> and math.<function> (e.g. np.log10 or math.sqrt). Once you click 'OK', you will be able to name the new component. + + + Qt::AlignJustify|Qt::AlignVCenter true @@ -43,231 +40,62 @@ - - - Qt::Vertical - - - QSizePolicy::Fixed + + + + + + + + + 100 + 16777215 + + + + Insert + + + + + + + + + + 0 + 0 + - + - 10 - 5 + 16777215 + 16777215 - - - - - - 0 - - - - Pre-defined transformations - - - - 5 - - - 10 - - - 10 - - - 10 - - - 10 - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 75 - true - - - - Info - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - Free-from expression - - - - 10 - - - 10 - - - 10 - - - 10 - - - - - In the box below, you can compose mathematical expressions that include components from the data. To add a component, select it in the dropdown list and click on 'Insert'. You can also type component names directly, provided that you surround them by curly braces, e.g. {x}. A status message below indicates whether the expression is valid. You can use any variable defined inside your config.py file, as well as numpy.<function>, np.<function> and math.<function> (e.g. np.log10 or math.sqrt). Once you click 'OK', you will be able to name the new component. - - - Qt::AlignJustify|Qt::AlignVCenter - - - true - - - - - - - - - - - - - 100 - 16777215 - - - - Insert - - - - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - - - Derived component name: - - - - - - - - 100 - 16777215 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + - + - status + Derived component name: + + + + + + + + 100 + 16777215 + - + Qt::Horizontal @@ -279,23 +107,6 @@ - - - - Cancel - - - - - - - OK - - - true - - - diff --git a/glue/dialogs/component_manager/qt/function_editor.py b/glue/dialogs/component_manager/qt/function_editor.py new file mode 100644 index 000000000..aab5a689b --- /dev/null +++ b/glue/dialogs/component_manager/qt/function_editor.py @@ -0,0 +1,188 @@ +from __future__ import absolute_import, division, print_function + +import os +from collections import OrderedDict + +try: + from inspect import getfullargspec +except ImportError: # Python 2.7 + from inspect import getargspec as getfullargspec + +from qtpy import QtWidgets + +from glue.config import link_function, link_helper +from glue.utils.qt import load_ui, update_combobox + +__all__ = ['FunctionEditorDialog'] + + +def get_function_name(item): + if hasattr(item, 'display') and item.display is not None: + return item.display + else: + return item.__name__ + + +def function_label(function): + """ Provide a label for a function + + :param function: A member from the glue.config.link_function registry + """ + args = getfullargspec(function.function)[0] + args = ', '.join(args) + output = function.output_labels + output = ', '.join(output) + label = "Link from %s to %s" % (args, output) + return label + + +def helper_label(helper): + """ Provide a label for a link helper + + :param helper: A member from the glue.config.link_helper registry + """ + return helper.info + + +class FunctionEditorDialog(QtWidgets.QDialog): + + def __init__(self, data=None, references=None, parent=None): + + super(FunctionEditorDialog, self).__init__(parent=parent) + + self.ui = load_ui('function_editor.ui', self, + directory=os.path.dirname(__file__)) + + # Get mapping from label to component ID + if references is not None: + self.references = references + elif data is not None: + self.references = OrderedDict() + for cid in data.primary_components: + self.references[cid.label] = cid + + # Populate category combo + f = [f for f in link_function.members if len(f.output_labels) == 1] + categories = sorted(set(l.category for l in f + link_helper.members)) + for category in categories: + self.ui.combosel_category.addItem(category) + self.ui.combosel_category.setCurrentIndex(0) + self.ui.combosel_category.currentIndexChanged.connect(self._populate_function_combo) + self._populate_function_combo() + + self.ui.combosel_function.setCurrentIndex(0) + self.ui.combosel_function.currentIndexChanged.connect(self._setup_inputs) + self._setup_inputs() + + @property + def category(self): + return self.ui.combosel_category.currentText() + + @property + def function(self): + return self.ui.combosel_function.currentData() + + @property + def is_helper(self): + return self.function is not None and type(self.function).__name__ == 'LinkHelper' + + @property + def is_function(self): + return self.function is not None and type(self.function).__name__ == 'LinkFunction' + + def _setup_inputs(self, event=None): + + if self.is_function: + label = function_label(self.function) + input_labels = getfullargspec(self.function.function)[0] + + else: + label = helper_label(self.function) + input_labels = self.function.input_labels + + self.ui.label_info.setText(label) + + self._clear_input_output_layouts() + + input_message = "The function above takes the following input(s):" + + if len(input_labels) > 1: + input_message = input_message.replace('(s)', 's') + else: + input_message = input_message.replace('(s)', '') + + self.ui.layout_inout.addWidget(QtWidgets.QLabel(input_message), 0, 1, 1, 3) + + spacer1 = QtWidgets.QSpacerItem(10, 5, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Fixed) + spacer2 = QtWidgets.QSpacerItem(10, 5, + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Fixed) + self.ui.layout_inout.addItem(spacer1, 0, 0) + self.ui.layout_inout.addItem(spacer2, 0, 4) + + row = 0 + for a in input_labels: + row += 1 + self._add_input_widget(a, row) + + output_message = "This function produces the following output(s) - you can set the label(s) here:" + + if len(self.function.output_labels) > 1: + output_message = output_message.replace('(s)', 's') + else: + output_message = output_message.replace('(s)', '') + + row += 1 + self.ui.layout_inout.addWidget(QtWidgets.QLabel(output_message), row, 1, 1, 3) + + for a in self.function.output_labels: + row += 1 + self._add_output_widget(a, row) + + def _clear_input_output_layouts(self): + + for row in range(self.ui.layout_inout.rowCount()): + for col in range(self.ui.layout_inout.columnCount()): + item = self.ui.layout_inout.itemAtPosition(row, col) + if item is not None: + self.ui.layout_inout.removeItem(item) + if item.widget() is not None: + item.widget().setParent(None) + + def _add_input_widget(self, name, row): + label = QtWidgets.QLabel(name) + combo = QtWidgets.QComboBox() + update_combobox(combo, list(self.references.items())) + self.ui.layout_inout.addWidget(label, row, 1) + self.ui.layout_inout.addWidget(combo, row, 2) + + def _add_output_widget(self, name, row): + label = QtWidgets.QLabel(name) + edit = QtWidgets.QLineEdit() + self.ui.layout_inout.addWidget(label, row, 1) + self.ui.layout_inout.addWidget(edit, row, 2) + + def _populate_function_combo(self, event=None): + """ + Add name of functions to function combo box + """ + f = [f for f in link_function.members if len(f.output_labels) == 1] + functions = ((get_function_name(l[0]), l) for l in f + link_helper.members if l.category == self.category) + update_combobox(self.ui.combosel_function, functions) + self._setup_inputs() + + +if __name__ == "__main__": # pragma: nocover + + from glue.main import load_plugins + from glue.utils.qt import get_qapp + + app = get_qapp() + load_plugins() + + from glue.core.data import Data + d = Data(label='test1', x=[1, 2, 3], y=[2, 3, 4], z=[3, 4, 5]) + widget = FunctionEditorDialog(d) + widget.exec_() diff --git a/glue/dialogs/component_manager/qt/function_editor.ui b/glue/dialogs/component_manager/qt/function_editor.ui new file mode 100644 index 000000000..45e06fa7d --- /dev/null +++ b/glue/dialogs/component_manager/qt/function_editor.ui @@ -0,0 +1,111 @@ + + + Dialog + + + + 0 + 0 + 532 + 402 + + + + Derived Component Editor + + + + 5 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 75 + true + + + + Info + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + ColorizedCompletionTextEdit + QTextEdit +
glue.dialogs.component_manager.qt.equation_editor
+
+
+ + +
From b4e2a241904eff01a6af86a1f61ada257369752e Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Fri, 1 Dec 2017 17:27:10 +0000 Subject: [PATCH 5/5] WIP --- .../component_manager/qt/derived_creator.py | 62 +++++++ .../component_manager/qt/derived_creator.ui | 157 ++++++++++++++++++ .../component_manager/qt/equation_editor.py | 2 +- .../component_manager/qt/equation_editor.ui | 2 +- .../component_manager/qt/function_editor.ui | 2 +- 5 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 glue/dialogs/component_manager/qt/derived_creator.py create mode 100644 glue/dialogs/component_manager/qt/derived_creator.ui diff --git a/glue/dialogs/component_manager/qt/derived_creator.py b/glue/dialogs/component_manager/qt/derived_creator.py new file mode 100644 index 000000000..9b1906e45 --- /dev/null +++ b/glue/dialogs/component_manager/qt/derived_creator.py @@ -0,0 +1,62 @@ + +from __future__ import absolute_import, division, print_function + +import os +from collections import deque + +from qtpy import QtWidgets, QtCore +from qtpy.QtCore import Qt + +from glue.external.echo import CallbackProperty +from glue.core.parse import InvalidTagError, ParsedCommand, TAG_RE +from glue.utils.qt import load_ui, CompletionTextEdit + +__all__ = ['EquationEditorDialog'] + + +class DerivedComponentEditor(QtWidgets.QDialog): + + def __init__(self, equation=None, references=None, parent=None): + + super(DerivedComponentEditor, self).__init__(parent=parent) + + self.ui = load_ui('derived_creator.ui', self, + directory=os.path.dirname(__file__)) + + # Populate component combo + for label, cid in self.references.items(): + self.ui.combosel_component.addItem(label, userData=cid) + + # Set up labels for auto-completion + labels = ['{' + label + '}' for label in self.references] + self.ui.expression.set_word_list(labels) + + # Set initial equation + self.ui.expression.insertPlainText(equation) + + self.ui.button_insert.clicked.connect(self._insert_component) + + self.ui.expression.updated.connect(self._update_status) + self._update_status() + + + # Get mapping from label to component ID + if references is not None: + self.references = references + elif data is not None: + self.references = OrderedDict() + for cid in data.primary_components: + self.references[cid.label] = cid + +if __name__ == "__main__": # pragma: nocover + + from glue.main import load_plugins + from glue.utils.qt import get_qapp + + app = get_qapp() + load_plugins() + + from glue.core.data import Data + d = Data(label='test1', x=[1, 2, 3], y=[2, 3, 4], z=[3, 4, 5]) + widget = EquationEditorDialog(d, '') + widget.exec_() diff --git a/glue/dialogs/component_manager/qt/derived_creator.ui b/glue/dialogs/component_manager/qt/derived_creator.ui new file mode 100644 index 000000000..8e76791c5 --- /dev/null +++ b/glue/dialogs/component_manager/qt/derived_creator.ui @@ -0,0 +1,157 @@ + + + Dialog + + + + 0 + 0 + 535 + 378 + + + + Derived Component Editor + + + + 5 + + + QLayout::SetDefaultConstraint + + + 10 + + + 5 + + + 10 + + + 5 + + + + + Derived components are components that are evaluated on-the-fly based on other components. You can either define these using free-form expressions, or existing pre-defined transformations. + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 5 + + + + + + + + 0 + + + + Pre-defined transformations + + + + 5 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + Free-from expression + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + + + + + + status + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + OK + + + true + + + + + + + + + + diff --git a/glue/dialogs/component_manager/qt/equation_editor.py b/glue/dialogs/component_manager/qt/equation_editor.py index 89acd56c3..58d0a4743 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.py +++ b/glue/dialogs/component_manager/qt/equation_editor.py @@ -89,7 +89,7 @@ def format_components(m): self._cache = self.toPlainText() -class EquationEditorDialog(QtWidgets.QDialog): +class EquationEditorDialog(QtWidgets.QWidget): valid = CallbackProperty('') message = CallbackProperty('') diff --git a/glue/dialogs/component_manager/qt/equation_editor.ui b/glue/dialogs/component_manager/qt/equation_editor.ui index 765740b5a..92a3e5922 100644 --- a/glue/dialogs/component_manager/qt/equation_editor.ui +++ b/glue/dialogs/component_manager/qt/equation_editor.ui @@ -1,7 +1,7 @@ Dialog - + 0 diff --git a/glue/dialogs/component_manager/qt/function_editor.ui b/glue/dialogs/component_manager/qt/function_editor.ui index 45e06fa7d..fd29d0a97 100644 --- a/glue/dialogs/component_manager/qt/function_editor.ui +++ b/glue/dialogs/component_manager/qt/function_editor.ui @@ -1,7 +1,7 @@ Dialog - + 0