1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
|
from __future__ import absolute_import, division, print_function
import os
from inspect import getargspec
from qtpy import QtWidgets
from qtpy import PYSIDE
from glue import core
from glue.config import link_function, link_helper
from glue.utils import nonpartial
from glue.utils.qt import load_ui, messagebox_on_error, update_combobox
from glue.utils.qt.widget_properties import CurrentComboTextProperty, CurrentComboDataProperty
__all__ = ['LinkEquation']
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 = getargspec(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 ArgumentWidget(QtWidgets.QWidget):
def __init__(self, argument, parent=None):
super(ArgumentWidget, self).__init__(parent)
self.layout = QtWidgets.QHBoxLayout()
self.layout.setContentsMargins(1, 0, 1, 1)
self.setLayout(self.layout)
label = QtWidgets.QLabel(argument)
self._label = label
self._component_id = None
self.layout.addWidget(label)
self.editor = QtWidgets.QLineEdit()
self.editor.setReadOnly(True)
try:
self.editor.setPlaceholderText("Drag a component from above")
except AttributeError: # feature added in Qt 4.7
pass
self.layout.addWidget(self.editor)
self.setAcceptDrops(True)
@property
def component_id(self):
return self._component_id
@component_id.setter
def component_id(self, cid):
self._component_id = cid
self.editor.setText(str(cid))
@property
def label(self):
return self._label.text()
@label.setter
def label(self, label):
self._label.setText(label)
@property
def editor_text(self):
return self.editor.text()
def clear(self):
self.component_id = None
self.editor.clear()
def dragEnterEvent(self, event):
if event.mimeData().hasFormat('application/py_instance'):
event.accept()
else:
event.ignore()
def dropEvent(self, event):
obj = event.mimeData().data('application/py_instance')
if isinstance(obj, list):
obj = obj[0]
if not isinstance(obj, core.data.ComponentID):
event.ignore()
return
self.component_id = obj
event.accept()
class LinkEquation(QtWidgets.QWidget):
""" Interactively define ComponentLinks from existing functions
This widget inspects the calling signatures of helper functions,
and presents the user with an interface for assigning componentIDs
to the input and output arguments. It also generates ComponentLinks
from this information.
ComponentIDs are assigned to arguments via drag and drop. This
widget is used within the LinkEditor dialog
Usage::
widget = LinkEquation()
"""
category = CurrentComboTextProperty('_ui.category')
function = CurrentComboDataProperty('_ui.function')
def __init__(self, parent=None):
super(LinkEquation, self).__init__(parent)
# Set up mapping of function/helper name -> function/helper tuple. For the helpers, we use the 'display' name if available.
self._argument_widgets = []
self.spacer = None
self._output_widget = ArgumentWidget("")
# pyqt4 can't take self as second argument here
# for some reason. Manually embed
self._ui = load_ui('link_equation.ui', None,
directory=os.path.dirname(__file__))
l = QtWidgets.QHBoxLayout()
l.addWidget(self._ui)
self.setLayout(l)
self._init_widgets()
self._populate_category_combo()
self.category = 'General'
self._populate_function_combo()
self._connect()
self._setup_editor()
def set_result_visible(self, state):
self._ui.output_canvas.setVisible(state)
self._ui.output_label.setVisible(state)
def is_helper(self):
return self.function is not None and \
type(self.function).__name__ == 'LinkHelper'
def is_function(self):
return self.function is not None and \
type(self.function).__name__ == 'LinkFunction'
def _init_widgets(self):
layout = QtWidgets.QVBoxLayout()
layout.setSpacing(1)
self._ui.input_canvas.setLayout(layout)
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(1, 0, 1, 1)
self._ui.output_canvas.setLayout(layout)
layout.addWidget(self._output_widget)
spacer = QtWidgets.QSpacerItem(5, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
layout.addItem(spacer)
@property
def add_button(self):
return self._ui.addButton
@property
def signature(self):
""" Returns the ComponentIDs assigned to the input and output arguments
:rtype: tuple of (input, output). Input is a list of ComponentIDs.
output is a ComponentID
"""
inp = [a.component_id for a in self._argument_widgets]
out = self._output_widget.component_id
return inp, out
@signature.setter
def signature(self, inout):
inp, out = inout
for i, a in zip(inp, self._argument_widgets):
a.component_id = i
self._output_widget.component_id = out
@messagebox_on_error("Failed to create links")
def links(self):
""" Create ComponentLinks from the state of the widget
:rtype: list of ComponentLinks that can be created.
If no links can be created (e.g. because of missing input),
the empty list is returned
"""
inp, out = self.signature
if self.is_function():
using = self.function.function
if not all(inp) or not out:
return []
link = core.component_link.ComponentLink(inp, out, using)
return [link]
if self.is_helper():
helper = self.function.helper
if not all(inp):
return []
return helper(*inp)
def _update_add_enabled(self):
state = True
for a in self._argument_widgets:
state = state and a.component_id is not None
if self.is_function():
state = state and self._output_widget.component_id is not None
self._ui.addButton.setEnabled(state)
def _connect(self):
signal = self._ui.function.currentIndexChanged
signal.connect(nonpartial(self._setup_editor))
signal.connect(nonpartial(self._update_add_enabled))
self._output_widget.editor.textChanged.connect(nonpartial(self._update_add_enabled))
self._ui.category.currentIndexChanged.connect(self._populate_function_combo)
def clear_inputs(self):
for w in self._argument_widgets:
w.clear()
self._output_widget.clear()
def _setup_editor(self):
if self.is_function():
self._setup_editor_function()
else:
self._setup_editor_helper()
def _setup_editor_function(self):
""" Prepare the widget for the active function."""
assert self.is_function()
self.set_result_visible(True)
func = self.function.function
args = getargspec(func)[0]
label = function_label(self.function)
self._ui.info.setText(label)
self._output_widget.label = self.function.output_labels[0]
self._clear_input_canvas()
for a in args:
self._add_argument_widget(a)
self.spacer = QtWidgets.QSpacerItem(5, 5, QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self._ui.input_canvas.layout().addItem(self.spacer)
def _setup_editor_helper(self):
"""Setup the editor for the selected link helper"""
assert self.is_helper()
self.set_result_visible(False)
label = helper_label(self.function)
args = self.function.input_labels
self._ui.info.setText(label)
self._clear_input_canvas()
for a in args:
self._add_argument_widget(a)
self.spacer = QtWidgets.QSpacerItem(5, 5, QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
self._ui.input_canvas.layout().addItem(self.spacer)
def _add_argument_widget(self, argument):
""" Create and add a single argument widget to the input canvas
:param arguement: The argument name (string)
"""
widget = ArgumentWidget(argument)
widget.editor.textChanged.connect(nonpartial(self._update_add_enabled))
self._ui.input_canvas.layout().addWidget(widget)
self._argument_widgets.append(widget)
def _clear_input_canvas(self):
""" Remove all widgets from the input canvas """
layout = self._ui.input_canvas.layout()
for a in self._argument_widgets:
layout.removeWidget(a)
a.close()
if not PYSIDE:
# PySide crashing here
layout.removeItem(self.spacer)
self._argument_widgets = []
def _populate_category_combo(self):
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))
update_combobox(self._ui.category, list(zip(categories, categories)))
def _populate_function_combo(self):
""" 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.function, functions)
|