Exemple #1
0
class ChoiceElement(MeasElement):
    """Base for elements that allow an arbitrary choice."""

    CACHE_KEY = ''
    SORT_KEY = lambda self, x: None  # keep previous ordering
    VALUES = []

    def createWidget(self, parent, client):
        if self.CACHE_KEY:
            values = client.getDeviceParam(*self.CACHE_KEY.split('/'))
            try:
                values = sorted(values or [], key=self.SORT_KEY)
            except TypeError:  # unsortable?
                pass
        else:
            values = self.VALUES
        self._values = values
        self._widget = QComboBox(parent)
        self._widget.addItems(self._values)
        if self.value is not None and self.value in self._values:
            self._widget.setCurrentIndex(self._values.index(self.value))
        elif self.value is None and self._values:
            self.value = self._values[0]
        self._widget.currentIndexChanged.connect(self._updateValue)
        return self._widget

    def _updateValue(self, index):
        self.value = self._values[index]
        self.changed.emit(self.value)
Exemple #2
0
class AliasWidget(QFrame):
    def __init__(self, parent, name, selections, preselect):
        QFrame.__init__(self, parent)
        self.name = name
        self.selections = selections
        layout = QHBoxLayout()
        layout.addWidget(QLabel(name, self))
        self.combo = QComboBox(self)
        self.combo.addItems(selections)
        if preselect in selections:
            self.combo.setCurrentIndex(selections.index(preselect))
        else:
            self.combo.setCurrentIndex(0)
        layout.addWidget(self.combo)
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

    def setSelections(self, selections, preselect):
        if selections != self.selections:
            self.selections = selections
            self.combo.clear()
            self.combo.addItems(selections)
            if preselect in selections:
                self.combo.setCurrentIndex(selections.index(preselect))
            else:
                self.combo.setCurrentIndex(0)

    def getSelection(self):
        return self.combo.currentText()
Exemple #3
0
class Detector(MeasElement):
    """Element for selecting detector distance, depending on selector."""

    CACHE_KEY = 'detector/presets'
    SORT_KEY = lambda self, x: num_sort(x)
    LABEL = 'Detector'

    _allvalues = None

    def clientUpdate(self, client):
        self._allvalues = client.getDeviceParam(*self.CACHE_KEY.split('/'))
        self._values = []

    def createWidget(self, parent, client):
        self.clientUpdate(client)
        self._widget = QComboBox(parent)
        self._updateWidget()
        self._widget.currentIndexChanged.connect(self._updateValue)
        return self._widget

    def _updateWidget(self):
        self._widget.clear()
        self._widget.addItems(self._values)
        if self.value in self._values:
            self._widget.setCurrentIndex(self._values.index(self.value))

    def otherChanged(self, eltype, value):
        if eltype == 'selector' and self._allvalues is not None:
            self._values = sorted(self._allvalues[value], key=self.SORT_KEY)
            if self.value not in self._values:
                if self._values:
                    self.value = self._values[0]
                else:
                    self.value = None
            if self._widget is not None:
                self._updateWidget()

    def _updateValue(self, index):
        self.value = self._values[index]
        self.changed.emit(self.value)
Exemple #4
0
class DataExportDialog(QFileDialog):
    def __init__(self, viewplot, curvenames, *args):
        QFileDialog.__init__(self, viewplot, *args)
        self.setOption(self.DontConfirmOverwrite, False)
        # allow adding some own widgets
        self.setOption(self.DontUseNativeDialog, True)
        self.setAcceptMode(QFileDialog.AcceptSave)
        layout = self.layout()
        layout.addWidget(QLabel('Curve:', self), 4, 0)
        self.curveCombo = QComboBox(self)
        if len(curvenames) > 1:
            self.curveCombo.addItem('all (in separate files)')
            self.curveCombo.addItem('all (in one file, multiple data columns)')
        self.curveCombo.addItems(curvenames)
        layout.addWidget(self.curveCombo, 4, 1)
        layout.addWidget(QLabel('Time format:', self), 5, 0)
        self.formatCombo = QComboBox(self)
        self.formatCombo.addItems([
            'Seconds since first datapoint', 'UNIX timestamp',
            'Text timestamp (YYYY-MM-dd.HH:MM:SS)'
        ])
        layout.addWidget(self.formatCombo, 5, 1)
Exemple #5
0
class Chopper(MeasElement):
    """Element for selecting chopper TOF resolution."""

    CACHE_KEY = 'chopper/resolutions'
    SORT_KEY = lambda self, x: num_sort(x)
    LABEL = 'TOF dλ/λ'

    def createWidget(self, parent, client):
        resos = client.getDeviceParam(*self.CACHE_KEY.split('/'))
        self._values = ['off'] + ['%.1f%%' % v
                                  for v in (resos or [])] + ['manual']
        self._widget = QComboBox(parent)
        self._widget.addItems(self._values)
        if self.value is not None and self.value in self._values:
            self._widget.setCurrentIndex(self._values.index(self.value))
        elif self.value is None and self._values:
            self.value = self._values[0]
        self._widget.currentIndexChanged.connect(self._updateValue)
        return self._widget

    def _updateValue(self, index):
        self.value = self._values[index]
        self.changed.emit(self.value)
Exemple #6
0
class ControlDialog(QDialog):
    """Dialog opened to control and view details for one device."""

    closed = pyqtSignal(object)

    def __init__(self, parent, devname, devinfo, devitem, log, expert):
        QDialog.__init__(self, parent)
        loadUi(self, 'panels/devices_one.ui')
        self.log = log

        self.device_panel = parent
        self.client = parent.client
        self.devname = devname
        self.devinfo = devinfo
        self.devitem = devitem
        self.paramItems = {}
        self.moveBtn = None
        self.target = None

        self._reinit()
        self._show_extension(expert)

        if self.target:
            self.target.setFocus()

    def _reinit(self):
        classes = self.devinfo.classes

        if sip.isdeleted(self.devitem):
            # The item we're controlling has been removed from the list (e.g.
            # due to client reconnect), get it again.
            self.devitem = self.device_panel._devitems.get(
                self.devname.lower())
            # No such device anymore...
            if self.devitem is None:
                self.close()
                return

        self.deviceName.setText('Device: %s' % self.devname)
        self.setWindowTitle('Control %s' % self.devname)

        self.settingsBtn = self.buttonBox.button(
            QDialogButtonBox.RestoreDefaults)
        self.settingsBtn.clicked.connect(self.on_settingsBtn_clicked)

        # trigger parameter poll
        self.client.eval('%s.pollParams()' % self.devname, None)

        # now get all cache keys pertaining to the device and set the
        # properties we want
        params = self.client.getDeviceParams(self.devname)
        self.paraminfo = self.client.getDeviceParamInfo(self.devname)
        self.paramvalues = dict(params)

        # put parameter values in the list widget
        self.paramItems.clear()
        self.paramList.clear()
        for key, value in sorted(iteritems(params)):
            if self.paraminfo.get(key):
                # normally, show only userparams, except in expert mode
                is_userparam = self.paraminfo[key]['userparam']
                if is_userparam or self.device_panel._show_lowlevel:
                    self.paramItems[key] = item = \
                        QTreeWidgetItem(self.paramList, [key, str(value)])
                    # display non-userparams in grey italics, like lowlevel
                    # devices in the device list
                    if not is_userparam:
                        item.setFont(0, lowlevelFont[True])
                        item.setForeground(0, lowlevelBrush[True])

        # set description label
        if params.get('description'):
            self.description.setText(params['description'])
        else:
            self.description.setVisible(False)

        # check how to refer to the device in commands: if it is lowlevel,
        # we need to use quotes
        self.devrepr = srepr(self.devname) if params.get('lowlevel', True) \
            else self.devname

        # show "Set alias" group box if it is an alias device
        if 'alias' in params:
            if params['alias']:
                self.deviceName.setText(self.deviceName.text() +
                                        ' (alias for %s)' % params['alias'])
            alias_config = self.client.eval('session.alias_config', {})
            self.aliasTarget = QComboBox(self)
            self.aliasTarget.setEditable(True)
            if self.devname in alias_config:
                items = [t[0] for t in alias_config[self.devname]]
                self.aliasTarget.addItems(items)
                if params['alias'] in items:
                    self.aliasTarget.setCurrentIndex(
                        items.index(params['alias']))
            self.targetLayoutAlias.takeAt(1).widget().deleteLater()
            self.targetLayoutAlias.insertWidget(1, self.aliasTarget)
            if self.client.viewonly:
                self.setAliasBtn.setEnabled(False)
        else:
            self.aliasGroup.setVisible(False)

        historyBtn = self.buttonBox.button(QDialogButtonBox.Reset)
        # show current value/status if it is readable
        if 'nicos.core.device.Readable' not in classes:
            self.valueFrame.setVisible(False)
            self.buttonBox.removeButton(historyBtn)
        else:
            self.valuelabel.setText(self.devitem.text(1))
            self.statuslabel.setText(self.devitem.text(2))
            self.statusimage.setPixmap(self.devitem.icon(0).pixmap(16, 16))
            setForegroundBrush(self.statuslabel, self.devitem.foreground(2))
            setBackgroundBrush(self.statuslabel, self.devitem.background(2))

            # modify history button: add icon and set text
            historyBtn.setIcon(QIcon(':/find'))
            historyBtn.setText('Plot history...')
            historyBtn.clicked.connect(self.on_historyBtn_clicked)

        if self.client.viewonly:
            self.limitFrame.setVisible(False)
            self.targetFrame.setVisible(False)
            return

        # add a menu for the "More" button
        self.moveBtns.clear()
        menu = QMenu(self)
        if 'nicos.core.mixins.HasLimits' in classes:
            menu.addAction(self.actionSetLimits)
        if 'nicos.core.mixins.HasOffset' in classes:
            menu.addAction(self.actionAdjustOffset)
        if 'nicos.devices.abstract.CanReference' in classes:
            menu.addAction(self.actionReference)
        if 'nicos.devices.abstract.Coder' in classes:
            menu.addAction(self.actionSetPosition)
        if 'nicos.core.device.Moveable' in classes:
            if not menu.isEmpty():
                menu.addSeparator()
            menu.addAction(self.actionFix)
            menu.addAction(self.actionRelease)
        if 'nicos.core.mixins.CanDisable' in classes:
            if not menu.isEmpty():
                menu.addSeparator()
            menu.addAction(self.actionEnable)
            menu.addAction(self.actionDisable)
        if not menu.isEmpty():
            menuBtn = QPushButton('More', self)
            menuBtn.setMenu(menu)
            self.moveBtns.addButton(menuBtn, QDialogButtonBox.ResetRole)

        def reset(checked):
            self.device_panel.exec_command('reset(%s)' % self.devrepr)

        def stop(checked):
            self.device_panel.exec_command('stop(%s)' % self.devrepr,
                                           immediate=True)

        self.moveBtns.addButton('Reset', QDialogButtonBox.ResetRole)\
                     .clicked.connect(reset)

        if 'nicos.core.device.Moveable' in classes or \
           'nicos.core.device.Measurable' in classes:
            self.moveBtns.addButton('Stop', QDialogButtonBox.ResetRole)\
                         .clicked.connect(stop)

        # show target and limits if the device is Moveable
        if 'nicos.core.device.Moveable' not in classes:
            self.limitFrame.setVisible(False)
            self.targetFrame.setVisible(False)
        else:
            if 'nicos.core.mixins.HasLimits' not in classes:
                self.limitFrame.setVisible(False)
            else:
                self.limitMin.setText(str(params['userlimits'][0]))
                self.limitMax.setText(str(params['userlimits'][1]))

            # insert a widget to enter a new device value
            # allowEnter=False because we catch pressing Enter ourselves
            self.target = DeviceValueEdit(self,
                                          dev=self.devname,
                                          useButtons=True,
                                          allowEnter=False)
            self.target.setClient(self.client)

            def btn_callback(target):
                self.device_panel.exec_command('move(%s, %s)' %
                                               (self.devrepr, srepr(target)))

            self.target.valueChosen.connect(btn_callback)
            self.targetFrame.layout().takeAt(1).widget().deleteLater()
            self.targetFrame.layout().insertWidget(1, self.target)

            def move(checked):
                try:
                    target = self.target.getValue()
                except ValueError:
                    return
                self.device_panel.exec_command('move(%s, %s)' %
                                               (self.devrepr, srepr(target)))

            if self.target.getValue() is not Ellipsis:  # (button widget)
                self.moveBtn = self.moveBtns.addButton(
                    'Move', QDialogButtonBox.AcceptRole)
                self.moveBtn.clicked.connect(move)
            else:
                self.moveBtn = None

            if params.get('fixed') and self.moveBtn:
                self.moveBtn.setEnabled(False)
                self.moveBtn.setText('(fixed)')

    def on_paramList_customContextMenuRequested(self, pos):
        item = self.paramList.itemAt(pos)
        if not item:
            return

        menu = QMenu(self)
        refreshAction = menu.addAction('Refresh')
        menu.addAction('Refresh all')

        # QCursor.pos is more reliable then the given pos
        action = menu.exec_(QCursor.pos())

        if action:
            cmd = 'session.getDevice(%r).pollParams(volatile_only=False%s)' \
                  % (self.devname, ', param_list=[%r]' % item.text(0)
                     if action == refreshAction else '')
            # poll even non volatile parameter as requested explicitely
            self.client.eval(cmd, None)

    @pyqtSlot()
    def on_settingsBtn_clicked(self):
        self._show_extension(self.extension.isHidden())

    def _show_extension(self, show):
        if show:
            # make "settings shown" permanent
            self.settingsBtn.hide()
        self.extension.setVisible(show)
        self.settingsBtn.setText('Settings %s' % ('<<<' if show else '>>>'))
        sz = self.size()
        sz.setHeight(self.sizeHint().height())
        self.resize(sz)

    @pyqtSlot()
    def on_actionSetLimits_triggered(self):
        dlg = dialogFromUi(self, 'panels/devices_limits.ui')
        dlg.descLabel.setText('Adjust user limits of %s:' % self.devname)
        dlg.limitMin.setText(self.limitMin.text())
        dlg.limitMax.setText(self.limitMax.text())
        abslimits = self.client.getDeviceParam(self.devname, 'abslimits')
        offset = self.client.getDeviceParam(self.devname, 'offset')
        if offset is not None:
            abslimits = abslimits[0] - offset, abslimits[1] - offset
        dlg.limitMinAbs.setText(str(abslimits[0]))
        dlg.limitMaxAbs.setText(str(abslimits[1]))
        target = DeviceParamEdit(dlg, dev=self.devname, param='userlimits')
        target.setClient(self.client)
        btn = dlg.buttonBox.addButton('Reset to maximum range',
                                      QDialogButtonBox.ResetRole)

        def callback():
            self.device_panel.exec_command('resetlimits(%s)' % self.devrepr)
            dlg.reject()

        btn.clicked.connect(callback)
        dlg.targetLayout.addWidget(target)
        res = dlg.exec_()
        if res != QDialog.Accepted:
            return
        newlimits = target.getValue()
        if newlimits[0] < abslimits[0] or newlimits[1] > abslimits[1]:
            QMessageBox.warning(
                self, 'Error', 'The entered limits are not '
                'within the absolute limits for the device.')
            # retry
            self.on_actionSetLimits_triggered()
            return
        self.device_panel.exec_command('set(%s, "userlimits", %s)' %
                                       (self.devrepr, newlimits))

    def _get_new_value(self, window_title, desc):
        dlg = dialogFromUi(self, 'panels/devices_newpos.ui')
        dlg.setWindowTitle(window_title)
        dlg.descLabel.setText(desc)
        dlg.oldValue.setText(self.valuelabel.text())
        target = DeviceValueEdit(dlg, dev=self.devname)
        target.setClient(self.client)
        dlg.targetLayout.addWidget(target)
        target.setFocus()
        res = dlg.exec_()
        if res != QDialog.Accepted:
            return None
        return target.getValue()

    @pyqtSlot()
    def on_actionAdjustOffset_triggered(self):
        val = self._get_new_value('Adjust NICOS offset',
                                  'Adjust NICOS offset of %s:' % self.devname)
        if val is not None:
            self.device_panel.exec_command('adjust(%s, %r)' %
                                           (self.devrepr, val))

    @pyqtSlot()
    def on_actionSetPosition_triggered(self):
        val = self._get_new_value(
            'Set hardware position',
            'Set hardware position of %s:' % self.devname)
        if val is not None:
            if self.devrepr != self.devname:
                cmd = 'CreateDevice(%s); %s.setPosition(%r)' % \
                      (self.devrepr, self.devname, val)
            else:
                cmd = '%s.setPosition(%r)' % (self.devname, val)
            self.device_panel.exec_command(cmd)

    @pyqtSlot()
    def on_actionReference_triggered(self):
        self.device_panel.exec_command('reference(%s)' % self.devrepr)

    @pyqtSlot()
    def on_actionFix_triggered(self):
        reason, ok = QInputDialog.getText(
            self, 'Fix',
            'Please enter the reason for fixing %s:' % self.devname)
        if not ok:
            return
        self.device_panel.exec_command('fix(%s, %r)' % (self.devrepr, reason))

    @pyqtSlot()
    def on_actionRelease_triggered(self):
        self.device_panel.exec_command('release(%s)' % self.devrepr)

    @pyqtSlot()
    def on_actionEnable_triggered(self):
        self.device_panel.exec_command('enable(%s)' % self.devrepr)

    @pyqtSlot()
    def on_actionDisable_triggered(self):
        self.device_panel.exec_command('disable(%s)' % self.devrepr)

    @pyqtSlot()
    def on_setAliasBtn_clicked(self):
        self.device_panel.exec_command(
            'set(%s, "alias", %s)' %
            (self.devrepr, srepr(self.aliasTarget.currentText())))

    def closeEvent(self, event):
        event.accept()
        self.closed.emit(self.devname.lower())

    def on_cache(self, subkey, value):
        if subkey not in self.paramItems:
            return
        if not value:
            return
        value = cache_load(value)
        self.paramvalues[subkey] = value
        self.paramItems[subkey].setText(1, str(value))

    def on_paramList_itemClicked(self, item):
        pname = item.text(0)
        self.editParam(pname)

    def editParam(self, pname):
        if not self.paraminfo[pname]['settable'] or self.client.viewonly:
            return
        mainunit = self.paramvalues.get('unit', 'main')
        punit = (self.paraminfo[pname]['unit'] or '').replace('main', mainunit)

        dlg = dialogFromUi(self, 'panels/devices_param.ui')
        dlg.target = DeviceParamEdit(self, dev=self.devname, param=pname)
        dlg.target.setClient(self.client)
        dlg.paramName.setText('Parameter: %s.%s' % (self.devname, pname))
        dlg.paramDesc.setText(self.paraminfo[pname]['description'])
        dlg.paramValue.setText(str(self.paramvalues[pname]) + ' ' + punit)
        dlg.targetLayout.addWidget(dlg.target)
        dlg.resize(dlg.sizeHint())
        dlg.target.setFocus()
        if dlg.exec_() != QDialog.Accepted:
            return
        try:
            new_value = dlg.target.getValue()
        except ValueError:
            self.log.exception('invalid value for typed value')
            # shouldn't happen, but if it does, at least give an indication that
            # something went wrong
            QMessageBox.warning(
                self, 'Error', 'The entered value is invalid '
                'for this parameter.')
            return
        if self.devrepr == self.devname:
            self.device_panel.exec_command('%s.%s = %r' %
                                           (self.devname, pname, new_value))
        else:
            self.device_panel.exec_command(
                'set(%s, %s, %r)' % (self.devrepr, srepr(pname), new_value))

    def on_historyBtn_clicked(self):
        self.device_panel.plot_history(self.devname)
Exemple #7
0
class ToftofProfileWindow(DlgUtils, QMainWindow):
    def __init__(self, parent):
        QMainWindow.__init__(self, parent)
        DlgUtils.__init__(self, 'Live data')
        self.panel = parent
        layout1 = QVBoxLayout()
        self.plot = QwtPlot(self)
        layout1.addWidget(self.plot)
        self.curve = QwtPlotCurve()
        self.curve.setRenderHint(QwtPlotCurve.RenderAntialiased)
        self.curve.attach(self.plot)
        self.marker = QwtPlotMarker()
        self.marker.attach(self.plot)
        self.markerpen = QPen(Qt.red)
        self.marker.setSymbol(
            QwtSymbol(QwtSymbol.Ellipse, QBrush(), self.markerpen, QSize(7,
                                                                         7)))
        self.zoomer = QwtPlotZoomer(self.plot.canvas())
        self.zoomer.setMousePattern(QwtPlotZoomer.MouseSelect3, Qt.NoButton)
        self.picker = QwtPlotPicker(self.plot.canvas())
        self.picker.setSelectionFlags(QwtPlotPicker.PointSelection
                                      | QwtPlotPicker.ClickSelection)
        self.picker.setMousePattern(QwtPlotPicker.MouseSelect1, Qt.MidButton)
        self.picker.selected.connect(self.pickerSelected)
        layout2 = QHBoxLayout()
        layout2.addWidget(QLabel('Scale:', self))
        self.scale = QComboBox(self)
        self.scale.addItems([
            'Single detectors, sorted by angle',
            'Scattering angle 2theta (deg)', 'Q value (A-1)'
        ])
        self.scale.currentIndexChanged[int].connect(self.scaleChanged)
        layout2.addWidget(self.scale)
        layout2.addStretch()
        self.scaleframe = QFrame(self)
        self.scaleframe.setLayout(layout2)
        self.scaleframe.setVisible(False)
        layout1.addWidget(self.scaleframe)
        mainframe = QFrame(self)
        mainframe.setLayout(layout1)
        self.setCentralWidget(mainframe)
        self.setContentsMargins(6, 6, 6, 6)
        plotfont = scaledFont(self.font(), 0.7)
        self.plot.setAxisFont(QwtPlot.xBottom, plotfont)
        self.plot.setAxisFont(QwtPlot.yLeft, plotfont)
        self.plot.setCanvasBackground(Qt.white)
        self.resize(800, 200)

        self._detinfo = None
        self._anglemap = None
        self._infowindow = None
        self._infolabel = None
        self._xs = self._ys = None
        self._type = None

    def _retrieve_detinfo(self):
        if self._detinfo is None:
            info = self.panel.client.eval(
                'det._detinfo_parsed, '
                'det._anglemap', None)
            if not info:
                return self.showError('Cannot retrieve detector info.')
            self._lambda = self.panel.client.eval('chWL()', None)
            if not self._lambda:
                return self.showError('Cannot retrieve wavelength.')
            self._detinfo, self._anglemap = info
            self._inverse_anglemap = 0
            self._infowindow = QMainWindow(self)
            self._infolabel = QLabel(self._infowindow)
            self._infolabel.setTextFormat(Qt.RichText)
            self._infowindow.setCentralWidget(self._infolabel)
            self._infowindow.setContentsMargins(10, 10, 10, 10)
            self._inv_anglemap = [[
                entry for entry in self._detinfo[1:]
                if entry[12] == self._anglemap[detnr] + 1
            ][0] for detnr in range(len(self._xs))]

    def scaleChanged(self, scale):
        self.update(self._type, self._orig_nbins, self._orig_x, self._orig_y)

    def update(self, proftype, nbins, x, y):
        self._orig_x = x
        self._orig_y = y
        self._orig_nbins = nbins
        x.setsize(8 * nbins)
        y.setsize(8 * nbins)
        xs = struct.unpack('d' * nbins, x)
        ys = struct.unpack('d' * nbins, y)
        if proftype == 0:
            if self.scale.currentIndex() == 0:
                xs = xs
            elif self.scale.currentIndex() == 1:
                self._retrieve_detinfo()
                xs = [self._inv_anglemap[int(xi)][5] for xi in xs]
            else:
                self._retrieve_detinfo()
                if self._lambda is None:
                    self.showError('Could not determine wavelength.')
                    self.scale.setCurrentIndex(1)
                    return
                xs = [
                    4 * pi / self._lambda *
                    sin(radians(self._inv_anglemap[int(xi)][5] / 2.))
                    for xi in xs
                ]
        self._xs = xs
        self._ys = ys
        self.curve.setData(xs, ys)
        self.plot.setAxisAutoScale(QwtPlot.xBottom)
        self.plot.setAxisAutoScale(QwtPlot.yLeft)
        self.marker.setVisible(False)
        self.zoomer.setZoomBase(True)
        self._type = proftype
        if proftype == 0:
            self.setWindowTitle(
                'Single detector view (time-channel integrated)')
            self.scaleframe.setVisible(True)
        elif proftype == 1:
            self.setWindowTitle('Time channel view (detector integrated)')
            self.scaleframe.setVisible(False)
        else:
            self.scaleframe.setVisible(False)

    def pickerSelected(self, point):
        if self._type != 0:
            return
        self._retrieve_detinfo()
        index = self.curve.closestPoint(self.picker.transform(point))[0]
        detentry = self._inv_anglemap[index][:]
        detentry.append(self._xs[index])
        detentry.append(self._ys[index])
        self.marker.setXValue(self._xs[index])
        self.marker.setYValue(self._ys[index])
        self.marker.setVisible(True)
        self.plot.replot()
        self._infowindow.show()
        entrynames = [
            'EntryNr', 'Rack', 'Plate', 'Pos', 'RPos', '2Theta', 'CableNr',
            'CableType', 'CableLen', 'CableEmpty', 'Card', 'Chan', 'Total',
            'DetName', 'BoxNr', 'BoxChan', 'XValue', 'Counts'
        ]
        formats = [
            '%s', '%d', '%d', '%d', '%d', '%.3f', '%d', '%d', '%.2f', '%d',
            '%d', '%d', '%d', '%r', '%d', '%d', '%s', '%d'
        ]
        empties = [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0]
        self._infolabel.setText('Detector info:<br><table>' + ''.join(
            '<tr><td>%s</td><td></td><td>%s</td></tr>%s' %
            (name, format % value, '<tr></tr>' if empty else '')
            for (name, format, empty,
                 value) in zip(entrynames, formats, empties, detentry)) +
                                '</table>')

    def closeEvent(self, event):
        if self._infowindow:
            self._infowindow.close()