예제 #1
0
    def __init__(self, parent):
        QMainWindow.__init__(self, parent)
        loadUi(self, 'browse.ui')
        self.logger = logger.getChild('browse')

        self.dataloader = parent
        self.rootdir = ''
        self.loader = Loader()
        self._data = {}
        self.yaxis = None
        self.canvas = MPLCanvas(self)
        self.canvas.plotter.lines = True
        self.toolbar = MPLToolbar(self.canvas, self)
        self.toolbar.setObjectName('browsetoolbar')
        self.addToolBar(self.toolbar)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.canvas)
        self.plotframe.setLayout(layout)
        self.sgroup = SettingGroup('browse')

        with self.sgroup as settings:
            geometry = settings.value('geometry', QByteArray())
            self.restoreGeometry(geometry)
            windowstate = settings.value('windowstate', QByteArray())
            self.restoreState(windowstate)
            splitstate = settings.value('splitstate', QByteArray())
            self.splitter.restoreState(splitstate)
            self.monScaleEdit.setText(settings.value('fixedmonval'))
예제 #2
0
    def __init__(self, parent):
        """ Constructing a basic QApplication
        """
        QMainWindow.__init__(self, parent)
        self.sgroup = SettingGroup('main')
        self.ui = uic.loadUi(path.join(path.dirname(__file__), 'ui', 'qexplorer.ui'), self)
        self.addWidgets()
        self.ui.show()
        self.dir = path.dirname(__file__)
        self.pts = []
        self.canvas = MPLCanvas(self)
        self.canvas.plotter.lines = True
        self.toolbar = MPLToolbar(self.canvas, self)
        self.toolbar.setObjectName('browsetoolbar')
        self.addToolBar(self.toolbar)
        self.v1 = [1, 0, 0]
        self.v2 = [0, 0, 1]
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.canvas)
        self.frmPlot.setLayout(layout)
        self.Reader = None

        # restore settings
        with self.sgroup as settings:
            data_template_path = settings.value('last_data_template', '')
            print(settings.value('test', ''))
            if data_template_path:
                dtempl, numor = extract_template(data_template_path)
                self.dir = dtempl
                self.ui.txtNumors.setText(str(numor))
                print("directory set to", self.dir)
예제 #3
0
class FitterMain(QMainWindow):
    def __init__(self, model, data, fit=True, fit_kws={}):
        QMainWindow.__init__(self)
        layout = QSplitter(Qt.Vertical, self)
        self.canvas = MPLCanvas(self)
        self.toolbar = MPLToolbar(self.canvas, self)
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        self.fitter = Fitter(self, standalone=True, fit_kws=fit_kws)
        self.canvas.mpl_connect('button_press_event',
                                self.fitter.on_canvas_pick)
        self.fitter.closeRequest.connect(self.close)
        self.fitter.replotRequest.connect(self.replot)
        self.fitter.initialize(model, data, fit)
        layout.addWidget(self.fitter)
        self.setCentralWidget(layout)
        self.setWindowTitle('Fitting: data %s' % data.name)

    def replot(self, limits=True):
        plotter = self.canvas.plotter
        plotter.reset(limits=limits)
        try:
            plotter.plot_data(self.fitter.data)
            plotter.plot_model_full(self.fitter.model, self.fitter.data)
        except Exception:
            logger.exception('Error while plotting')
        else:
            plotter.draw()
예제 #4
0
 def __init__(self, model, data, fit=True, fit_kws={}):
     QMainWindow.__init__(self)
     layout = QSplitter(Qt.Vertical, self)
     self.canvas = MPLCanvas(self)
     self.toolbar = MPLToolbar(self.canvas, self)
     layout.addWidget(self.toolbar)
     layout.addWidget(self.canvas)
     self.fitter = Fitter(self, standalone=True, fit_kws=fit_kws)
     self.canvas.mpl_connect('button_press_event',
                             self.fitter.on_canvas_pick)
     self.fitter.closeRequest.connect(self.close)
     self.fitter.replotRequest.connect(self.replot)
     self.fitter.initialize(model, data, fit)
     layout.addWidget(self.fitter)
     self.setCentralWidget(layout)
     self.setWindowTitle('Fitting: data %s' % data.name)
예제 #5
0
파일: main.py 프로젝트: McStasMcXtrace/ufit
 def on_actionPopOut_triggered(self):
     new_win = QMainWindow()
     canvas = MPLCanvas(new_win)
     toolbar = MPLToolbar(canvas, new_win)
     new_win.addToolBar(toolbar)
     splitter = QSplitter(Qt.Vertical, new_win)
     splitter.addWidget(canvas)
     try:
         from ufit.gui.console import QIPythonWidget
     except ImportError:
         lbl = QLabel(
             'Please install IPython with qtconsole to '
             'activate the console part of this window.', new_win)
         splitter.addWidget(lbl)
     else:
         iw = QIPythonWidget('interactive pylab plotting prompt', new_win)
         iw.pushVariables({'ax': canvas.axes})
         iw.executeCommand('from ufit.lab import *')
         iw.executeCommand('sca(ax)')
         iw.setFocus()
         iw.redrawme.connect(canvas.draw_idle)
         splitter.addWidget(iw)
     new_win.setCentralWidget(splitter)
     self.current_panel.plot(limits=None, canvas=canvas)
     new_win.show()
예제 #6
0
 def __init__(self, data):
     QMainWindow.__init__(self)
     layout = QSplitter(Qt.Vertical, self)
     self.canvas = MPLCanvas(self)
     self.toolbar = MPLToolbar(self.canvas, self)
     layout.addWidget(self.toolbar)
     layout.addWidget(self.canvas)
     self.dloader = DataLoader(self, self.canvas.plotter, standalone=True)
     self.dloader.groupBox.hide()
     self.dloader.groupBoxLbl.hide()
     self.dloader.groupBoxDesc.hide()
     self.dloader.initialize()
     self.dloader.closeRequest.connect(self.close)
     layout.addWidget(self.dloader)
     self.setCentralWidget(layout)
     self.setWindowTitle('Data loading')
예제 #7
0
class BrowseWindow(QMainWindow):
    def __init__(self, parent):
        QMainWindow.__init__(self, parent)
        loadUi(self, 'browse.ui')
        self.logger = logger.getChild('browse')

        self.dataloader = parent
        self.rootdir = ''
        self.loader = Loader()
        self._data = {}
        self.yaxis = None
        self.canvas = MPLCanvas(self)
        self.canvas.plotter.lines = True
        self.toolbar = MPLToolbar(self.canvas, self)
        self.toolbar.setObjectName('browsetoolbar')
        self.addToolBar(self.toolbar)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.canvas)
        self.plotframe.setLayout(layout)
        self.sgroup = SettingGroup('browse')

        with self.sgroup as settings:
            geometry = settings.value('geometry', QByteArray())
            self.restoreGeometry(geometry)
            windowstate = settings.value('windowstate', QByteArray())
            self.restoreState(windowstate)
            splitstate = settings.value('splitstate', QByteArray())
            self.splitter.restoreState(splitstate)
            self.monScaleEdit.setText(settings.value('fixedmonval'))

    @pyqtSlot()
    def on_loadBtn_clicked(self):
        datas = [
            self._data[item.type()] for item in self.dataList.selectedItems()
        ]
        if not datas:
            return
        items = [ScanDataItem(data) for data in datas]
        session.add_items(items)

    @pyqtSlot()
    def on_addNumBtn_clicked(self):
        numors = [
            self._data[item.type()].meta['filenumber']
            for item in self.dataList.selectedItems()
        ]
        if not numors:
            return
        self.dataloader.add_numors(sorted(numors))

    @pyqtSlot()
    def on_dirBtn_clicked(self):
        newdir = QFileDialog.getExistingDirectory(self, 'New directory',
                                                  self.rootdir)
        self.set_directory(path_to_str(newdir))

    @pyqtSlot()
    def on_refreshBtn_clicked(self):
        self.set_directory(self.rootdir)

    @pyqtSlot()
    def on_monScaleBtn_clicked(self):
        self.set_directory(self.rootdir)

    @pyqtSlot()
    def on_yAxisBtn_clicked(self):
        self.set_directory(self.rootdir)

    def set_directory(self, root):
        self.setWindowTitle('ufit browser - %s' % root)
        self.canvas.axes.text(0.5,
                              0.5,
                              'Please wait, loading all data...',
                              horizontalalignment='center')
        self.canvas.draw()
        QApplication.processEvents()
        self.rootdir = root
        files = os.listdir(root)
        self.dataList.clear()
        for fn in sorted(files):
            fn = path.join(root, fn)
            if not path.isfile(fn):
                continue
            try:
                t, n = extract_template(fn)
                self.loader.template = t
                fixed_yaxis = self.yAxisEdit.text()
                yaxis = fixed_yaxis if (
                    fixed_yaxis and self.useYAxis.isChecked()) else 'auto'
                res = self.loader.load(n, 'auto', yaxis, 'auto', 'auto', -1)
            except Exception as e:
                self.logger.warning('While loading %r: %s' % (fn, e))
            else:
                if self.useMonScale.isChecked():
                    const = int(self.monScaleEdit.text())  # XXX check
                    res.rescale(const)
                if self.useFmtString.isChecked():
                    try:
                        scanLabel = self.fmtStringEdit.text().format(
                            n, FormatWrapper(res))
                    except Exception:
                        scanLabel = '%s (%s) - %s | %s' % (
                            n, res.xcol, res.title, ', '.join(res.environment))
                else:
                    scanLabel = '%s (%s) - %s | %s' % (
                        n, res.xcol, res.title, ', '.join(res.environment))
                self._data[n] = res
                QListWidgetItem(scanLabel, self.dataList, n)
        self.canvas.axes.clear()
        self.canvas.draw()

    def on_dataList_itemSelectionChanged(self):
        numors = [item.type() for item in self.dataList.selectedItems()]
        if not numors:
            return
        plotter = self.canvas.plotter
        plotter.reset(False)
        if len(numors) > 1:
            for numor in numors:
                plotter.plot_data(self._data[numor], multi=True)
            plotter.plot_finish()
        else:
            plotter.plot_data(self._data[numors[0]])
        plotter.draw()

    def closeEvent(self, event):
        event.accept()
        with self.sgroup as settings:
            settings.setValue('geometry', self.saveGeometry())
            settings.setValue('windowstate', self.saveState())
            settings.setValue('splitstate', self.splitter.saveState())
            settings.setValue('fixedmonval', self.monScaleEdit.text())
예제 #8
0
class ReciprocalViewer(QMainWindow):

    def __init__(self, parent):
        """ Constructing a basic QApplication
        """
        QMainWindow.__init__(self, parent)
        self.sgroup = SettingGroup('main')
        self.ui = uic.loadUi(path.join(path.dirname(__file__), 'ui', 'qexplorer.ui'), self)
        self.addWidgets()
        self.ui.show()
        self.dir = path.dirname(__file__)
        self.pts = []
        self.canvas = MPLCanvas(self)
        self.canvas.plotter.lines = True
        self.toolbar = MPLToolbar(self.canvas, self)
        self.toolbar.setObjectName('browsetoolbar')
        self.addToolBar(self.toolbar)
        self.v1 = [1, 0, 0]
        self.v2 = [0, 0, 1]
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.canvas)
        self.frmPlot.setLayout(layout)
        self.Reader = None

        # restore settings
        with self.sgroup as settings:
            data_template_path = settings.value('last_data_template', '')
            print(settings.value('test', ''))
            if data_template_path:
                dtempl, numor = extract_template(data_template_path)
                self.dir = dtempl
                self.ui.txtNumors.setText(str(numor))
                print("directory set to", self.dir)

    def closeEvent(self, event):
        with self.sgroup as settings:
            settings.setValue('test', 'abc')

    def addWidgets(self):
        """ Connecting signals """
        self.ui.btnLoad.clicked.connect(self.readData)
        self.ui.btnSelectDir.clicked.connect(self.changeDir)
        self.ui.btnShow.clicked.connect(self.readPoints)
        self.ui.btnAddBZ.clicked.connect(self.addBZ)
        self.ui.fltEmax.valueChanged.connect(self.showPoints)
        self.ui.fltEmin.valueChanged.connect(self.showPoints)
        self.ui.chkBigFont.stateChanged.connect(self.bigFont)

    def readData(self):
        """ This function will read data from files indicated by folder and Numors """

        numors = self.ui.txtNumors.text()
        self.canvas.axes.text(0.5, 0.5, 'Please wait, loading all data...',
                              horizontalalignment='center')
        self.canvas.draw()
        QApplication.processEvents()
        self.reader = qr.QReader(self.dir, numors)
        self.canvas.axes.clear()
        self.canvas.draw()

    def readVectors(self):
        """ Reading orientation vectors from text fields """

        self.v1 = [float(xx) for xx in str(self.ui.txtV1.text()).split()]
        self.v2 = [float(xx) for xx in str(self.ui.txtV2.text()).split()]

    def readPoints(self):
        """ It will find points in read scan files which has hkle values
        These is then converted to orientation vector basis and calculated distance from it.
        Only inplane points are processed.
        """

        self.readVectors()
        self.canvas.axes.text(0.5, 0.5, 'Please wait, parsing read data...',
                              horizontalalignment='center')
        self.canvas.draw()
        QApplication.processEvents()
        self.pts = self.reader.get_points(self.v1, self.v2)
        print("Datafiles read:", len(self.pts))
        self.canvas.axes.clear()
        self.canvas.draw()
        self.showPoints()

    def showPoints(self):
        """ Will show read points in selected E range to canvas  """
        plotter = self.canvas.plotter
        plotter.reset(False)
        x = []
        y = []
        emin = self.ui.fltEmin.value()
        emax = self.ui.fltEmax.value()

        for pt in self.pts:
            if pt[2] >= emin and pt[2] <= emax:  # ok, show point
                x.append(pt[0])
                y.append(pt[1])

        plotter.axes.plot(x, y, 'ro')
        plotter.axes.set_title('Measured points in reciprocal space', size='medium')
        plotter.axes.set_xlabel("x . (%.1f, %.1f, %.1f)" % tuple(self.v1))
        plotter.axes.set_ylabel("y . (%.1f, %.1f, %.1f)" % tuple(self.v2))
        plotter.draw()

    def changeDir(self):
        """ Change directory from which are the data read  """

        if self.dir:
            startdir = self.dir
        else:
            startdir = '.'
        fn = path_to_str(QFileDialog.getOpenFileName(
            self, 'Choose a file', startdir, 'All files (*)')[0])
        if not fn:
            return
        dtempl, numor = extract_template(fn)
        self.dir = dtempl
        self.ui.txtNumors.setText(str(numor))
        print("directory changed to", self.dir)

    def bigFont(self, state):
        """ Increase font size  """
        # TODO: should be done more sofisticated with more configuration

        ax = self.canvas.plotter.axes
        sizes = {
            "normal": [12, 10, 10],
            "big": [24, 22, 18]
        }
        if state == 0:  # normal size
            toset = sizes["normal"]
        else:
            toset = sizes["big"]

        ax.title.set_fontsize(toset[0])
        ax.xaxis.label.set_fontsize(toset[1])
        ax.yaxis.label.set_fontsize(toset[1])
        for t in ax.xaxis.get_major_ticks():
            t.label.set_fontsize(toset[2])
        for t in ax.yaxis.get_major_ticks():
            t.label.set_fontsize(toset[2])
        if (ax.legend_):
            for t in ax.legend_.texts:
                t.set_fontsize(toset[1])
        self.canvas.plotter.draw()

    def addBZ(self):
        """ Experimental: add brilluin zone. Only Body centered tetragonal now supported. """

        # check by vectors
        self.readVectors()
        myplane = np.cross(self.v1, self.v2)
        # TODO: generate gamma point dynamically
        gpts = np.array([[0, 0], [0, 2], [1, 1], [2, 0], [2, 2]])

        # TODO: read lattice parameters from file
        bzc = bp.BZCreator(gpts, a = 4.33148, c = 10.83387, plane = myplane)
        bzc.doPlot(self.canvas.plotter.axes)
        self.canvas.plotter.draw()
예제 #9
0
파일: main.py 프로젝트: McStasMcXtrace/ufit
    def __init__(self):
        QMainWindow.__init__(self)

        self.itempanels = {}
        self.multipanels = {}
        self.current_panel = None
        self.inspector_window = None
        self.annotation_window = None

        self.sgroup = SettingGroup('main')
        loadUi(self, 'main.ui')

        self.menuRecent.aboutToShow.connect(self.on_menuRecent_aboutToShow)
        self.menuMoveToGroup = QMenu('Move selected to group', self)
        self.menuMoveToGroup.setIcon(QIcon(':/drawer-open.png'))
        self.menuMoveToGroup.aboutToShow.connect(
            self.on_menuMoveToGroup_aboutToShow)
        self.menuCopyToGroup = QMenu('Copy selected to group', self)
        self.menuCopyToGroup.setIcon(QIcon(':/drawer-double-open.png'))
        self.menuCopyToGroup.aboutToShow.connect(
            self.on_menuCopyToGroup_aboutToShow)
        self.menuRemoveGroup = QMenu('Remove group', self)
        self.menuRemoveGroup.setIcon(QIcon(':/drawer--minus.png'))
        self.menuRemoveGroup.aboutToShow.connect(
            self.on_menuRemoveGroup_aboutToShow)
        self.menuRenameGroup = QMenu('Rename group', self)
        self.menuRenameGroup.aboutToShow.connect(
            self.on_menuRenameGroup_aboutToShow)

        # populate plot view
        layout2 = QVBoxLayout()
        layout2.setContentsMargins(0, 0, 0, 0)
        self.canvas = MPLCanvas(self, maincanvas=True)
        self.canvas.replotRequest.connect(
            lambda: self.current_panel.plot(True))
        self.canvas.mpl_connect('button_press_event', self.on_canvas_pick)
        self.canvas.mpl_connect('pick_event', self.on_canvas_pick)
        self.canvas.mpl_connect('button_release_event', self.on_canvas_click)
        self.toolbar = MPLToolbar(self.canvas, self)
        self.toolbar.setObjectName('mainplottoolbar')
        self.toolbar.popoutRequested.connect(self.on_actionPopOut_triggered)
        self.addToolBar(self.toolbar)
        layout2.addWidget(self.canvas)
        self.plotframe.setLayout(layout2)

        # session events
        session.itemsUpdated.connect(self.on_session_itemsUpdated)
        session.itemAdded.connect(self.on_session_itemAdded)
        session.filenameChanged.connect(self.on_session_filenameChanged)
        session.dirtyChanged.connect(self.on_session_dirtyChanged)

        # create data loader
        self.dloader = DataLoader(self, self.canvas.plotter)
        self.dloader.newDatas.connect(self.on_dloader_newDatas)
        self.stacker.addWidget(self.dloader)
        self.current_panel = self.dloader

        # create item model
        self.itemlistmodel = ItemListModel()
        self.itemTree.setModel(self.itemlistmodel)
        self.itemlistmodel.reset()
        self.itemTree.addAction(self.actionMergeData)
        self.itemTree.addAction(self.actionRemoveData)
        self.itemTree.addAction(self.menuMoveToGroup.menuAction())
        self.itemTree.addAction(self.menuCopyToGroup.menuAction())
        self.itemTree.newSelection.connect(self.on_itemTree_newSelection)

        # backend selector
        self.backend_group = QActionGroup(self)
        for backend in backends.available:
            action = QAction(backend.backend_name, self)
            action.setCheckable(True)
            if backends.backend.backend_name == backend.backend_name:
                action.setChecked(True)
            self.backend_group.addAction(action)
            self.menuBackend.addAction(action)
            action.triggered.connect(self.on_backend_action_triggered)

        # manage button
        menu = QMenu(self)
        menu.addAction(self.actionMergeData)
        menu.addAction(self.actionRemoveData)
        menu.addMenu(self.menuMoveToGroup)
        menu.addMenu(self.menuCopyToGroup)
        menu.addAction(self.actionReorder)
        menu.addSeparator()
        menu.addAction(self.actionNewGroup)
        menu.addMenu(self.menuRenameGroup)
        menu.addMenu(self.menuRemoveGroup)
        self.manageBtn.setMenu(menu)

        # right-mouse-button menu for canvas
        menu = self.canvasMenu = QMenu(self)
        menu.addAction(self.actionUnzoom)
        menu.addAction(self.actionHideFit)
        menu.addSeparator()
        menu.addAction(self.actionDrawSymbols)
        menu.addAction(self.actionConnectData)
        menu.addAction(self.actionSmoothImages)
        menu.addSeparator()
        menu.addAction(self.actionDrawGrid)
        menu.addAction(self.actionShowLegend)

        # restore window state
        with self.sgroup as settings:
            geometry = settings.value('geometry', QByteArray())
            self.restoreGeometry(geometry)
            windowstate = settings.value('windowstate', QByteArray())
            self.restoreState(windowstate)
            splitstate = settings.value('splitstate', QByteArray())
            self.splitter.restoreState(splitstate)
            vsplitstate = settings.value('vsplitstate', QByteArray())
            self.vsplitter.restoreState(vsplitstate)
            self.recent_files = settings.value('recentfiles', []) or []
예제 #10
0
파일: main.py 프로젝트: McStasMcXtrace/ufit
class UFitMain(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

        self.itempanels = {}
        self.multipanels = {}
        self.current_panel = None
        self.inspector_window = None
        self.annotation_window = None

        self.sgroup = SettingGroup('main')
        loadUi(self, 'main.ui')

        self.menuRecent.aboutToShow.connect(self.on_menuRecent_aboutToShow)
        self.menuMoveToGroup = QMenu('Move selected to group', self)
        self.menuMoveToGroup.setIcon(QIcon(':/drawer-open.png'))
        self.menuMoveToGroup.aboutToShow.connect(
            self.on_menuMoveToGroup_aboutToShow)
        self.menuCopyToGroup = QMenu('Copy selected to group', self)
        self.menuCopyToGroup.setIcon(QIcon(':/drawer-double-open.png'))
        self.menuCopyToGroup.aboutToShow.connect(
            self.on_menuCopyToGroup_aboutToShow)
        self.menuRemoveGroup = QMenu('Remove group', self)
        self.menuRemoveGroup.setIcon(QIcon(':/drawer--minus.png'))
        self.menuRemoveGroup.aboutToShow.connect(
            self.on_menuRemoveGroup_aboutToShow)
        self.menuRenameGroup = QMenu('Rename group', self)
        self.menuRenameGroup.aboutToShow.connect(
            self.on_menuRenameGroup_aboutToShow)

        # populate plot view
        layout2 = QVBoxLayout()
        layout2.setContentsMargins(0, 0, 0, 0)
        self.canvas = MPLCanvas(self, maincanvas=True)
        self.canvas.replotRequest.connect(
            lambda: self.current_panel.plot(True))
        self.canvas.mpl_connect('button_press_event', self.on_canvas_pick)
        self.canvas.mpl_connect('pick_event', self.on_canvas_pick)
        self.canvas.mpl_connect('button_release_event', self.on_canvas_click)
        self.toolbar = MPLToolbar(self.canvas, self)
        self.toolbar.setObjectName('mainplottoolbar')
        self.toolbar.popoutRequested.connect(self.on_actionPopOut_triggered)
        self.addToolBar(self.toolbar)
        layout2.addWidget(self.canvas)
        self.plotframe.setLayout(layout2)

        # session events
        session.itemsUpdated.connect(self.on_session_itemsUpdated)
        session.itemAdded.connect(self.on_session_itemAdded)
        session.filenameChanged.connect(self.on_session_filenameChanged)
        session.dirtyChanged.connect(self.on_session_dirtyChanged)

        # create data loader
        self.dloader = DataLoader(self, self.canvas.plotter)
        self.dloader.newDatas.connect(self.on_dloader_newDatas)
        self.stacker.addWidget(self.dloader)
        self.current_panel = self.dloader

        # create item model
        self.itemlistmodel = ItemListModel()
        self.itemTree.setModel(self.itemlistmodel)
        self.itemlistmodel.reset()
        self.itemTree.addAction(self.actionMergeData)
        self.itemTree.addAction(self.actionRemoveData)
        self.itemTree.addAction(self.menuMoveToGroup.menuAction())
        self.itemTree.addAction(self.menuCopyToGroup.menuAction())
        self.itemTree.newSelection.connect(self.on_itemTree_newSelection)

        # backend selector
        self.backend_group = QActionGroup(self)
        for backend in backends.available:
            action = QAction(backend.backend_name, self)
            action.setCheckable(True)
            if backends.backend.backend_name == backend.backend_name:
                action.setChecked(True)
            self.backend_group.addAction(action)
            self.menuBackend.addAction(action)
            action.triggered.connect(self.on_backend_action_triggered)

        # manage button
        menu = QMenu(self)
        menu.addAction(self.actionMergeData)
        menu.addAction(self.actionRemoveData)
        menu.addMenu(self.menuMoveToGroup)
        menu.addMenu(self.menuCopyToGroup)
        menu.addAction(self.actionReorder)
        menu.addSeparator()
        menu.addAction(self.actionNewGroup)
        menu.addMenu(self.menuRenameGroup)
        menu.addMenu(self.menuRemoveGroup)
        self.manageBtn.setMenu(menu)

        # right-mouse-button menu for canvas
        menu = self.canvasMenu = QMenu(self)
        menu.addAction(self.actionUnzoom)
        menu.addAction(self.actionHideFit)
        menu.addSeparator()
        menu.addAction(self.actionDrawSymbols)
        menu.addAction(self.actionConnectData)
        menu.addAction(self.actionSmoothImages)
        menu.addSeparator()
        menu.addAction(self.actionDrawGrid)
        menu.addAction(self.actionShowLegend)

        # restore window state
        with self.sgroup as settings:
            geometry = settings.value('geometry', QByteArray())
            self.restoreGeometry(geometry)
            windowstate = settings.value('windowstate', QByteArray())
            self.restoreState(windowstate)
            splitstate = settings.value('splitstate', QByteArray())
            self.splitter.restoreState(splitstate)
            vsplitstate = settings.value('vsplitstate', QByteArray())
            self.vsplitter.restoreState(vsplitstate)
            self.recent_files = settings.value('recentfiles', []) or []

    def on_dloader_newDatas(self, datas, ingroup):
        for group in session.groups:
            if group.name == ingroup:
                break
        else:
            group = session.add_group(ingroup)
        items = []
        for data in datas:
            if isinstance(data, ScanData):
                items.append(ScanDataItem(data))
            elif isinstance(data, ImageData):
                items.append(ImageDataItem(data))
            else:
                logger.warning('unknown data type: %s' % data)
        session.add_items(items, group)

    def on_session_itemsUpdated(self):
        # remove all panels whose item has vanished
        for item, panel in listitems(self.itempanels):
            if item not in session.all_items:
                self.stacker.removeWidget(panel)
                del self.itempanels[item]

    def on_session_itemAdded(self, item):
        # a single item has been added, show it
        self.itemTree.setCurrentIndex(self.itemlistmodel.index_for_item(item))

    def on_session_filenameChanged(self):
        if session.filename:
            self.setWindowTitle('ufit - %s[*]' % session.filename)
            self._add_recent_file(session.filename)
        else:
            self.setWindowTitle('ufit[*]')

    def on_session_dirtyChanged(self, dirty):
        self.setWindowModified(dirty)

    def _add_recent_file(self, fname):
        """Add to recent file list."""
        if not fname:
            return
        if fname in self.recent_files:
            self.recent_files.remove(fname)
        self.recent_files.insert(0, fname)
        if len(self.recent_files) > max_recent_files:
            self.recent_files.pop(-1)

    def on_menuRecent_aboutToShow(self):
        """Update recent file menu"""
        recent_files = []
        for fname in self.recent_files:
            if fname != session.filename and path.isfile(fname):
                recent_files.append(fname)
        self.menuRecent.clear()
        if recent_files:
            for i, fname in enumerate(recent_files):
                action = QAction('%d - %s' % (i + 1, fname), self)
                action.triggered.connect(self.load_session)
                action.setData(fname)
                self.menuRecent.addAction(action)
        self.actionClearRecent.setEnabled(len(recent_files) > 0)
        self.menuRecent.addSeparator()
        self.menuRecent.addAction(self.actionClearRecent)

    @pyqtSlot()
    def on_actionClearRecent_triggered(self):
        """Clear recent files list"""
        self.recent_files = []

    def select_new_panel(self, panel):
        if panel is not self.current_panel:
            if hasattr(self.current_panel, 'save_limits'):
                self.current_panel.save_limits()
            self.current_panel = panel
        self.stacker.setCurrentWidget(self.current_panel)  # doesn't hurt

    def on_canvas_pick(self, event):
        if isinstance(self.current_panel, ScanDataPanel):
            self.current_panel.on_canvas_pick(event)

    def on_canvas_click(self, event):
        if event.button == 3 and not self.toolbar.mode:  # right button
            self.canvasMenu.popup(QCursor.pos())

    @pyqtSlot()
    def on_loadBtn_clicked(self):
        self.select_new_panel(self.dloader)
        self.itemTree.setCurrentIndex(QModelIndex())

    @pyqtSlot()
    def on_actionNewGroup_triggered(self):
        name = QInputDialog.getText(
            self, 'ufit', 'Please enter a name '
            'for the new group:')[0]
        if not name:
            return
        session.add_group(name)

    def on_menuMoveToGroup_aboutToShow(self):
        self.menuMoveToGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def move_to(_arg=None, group=group):
                items = self.selected_items()
                if not items:
                    return
                session.move_items(items, group)
                self.re_expand_tree()

            action.triggered.connect(move_to)
            self.menuMoveToGroup.addAction(action)

    def on_menuCopyToGroup_aboutToShow(self):
        self.menuCopyToGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def copy_to(_arg=None, group=group):
                items = self.selected_items()
                if not items:
                    return
                session.copy_items(items, group)
                self.re_expand_tree()

            action.triggered.connect(copy_to)
            self.menuCopyToGroup.addAction(action)

    def on_menuRemoveGroup_aboutToShow(self):
        self.menuRemoveGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def remove(_arg=None, group=group):
                session.remove_group(group)
                self.re_expand_tree()

            action.triggered.connect(remove)
            self.menuRemoveGroup.addAction(action)

    def on_menuRenameGroup_aboutToShow(self):
        self.menuRenameGroup.clear()
        for group in session.groups:
            action = QAction(group.name, self)

            def rename(_arg=None, group=group):
                name = QInputDialog.getText(self,
                                            'ufit', 'Please enter a new name '
                                            'for the group:',
                                            text=group.name)[0]
                if not name:
                    return
                session.rename_group(group, name)

            action.triggered.connect(rename)
            self.menuRenameGroup.addAction(action)

    @pyqtSlot()
    def on_actionRemoveData_triggered(self):
        items = self.selected_items()
        if not items:
            return
        if QMessageBox.question(
                self, 'ufit', 'OK to remove %d item(s)?' % len(items),
                QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
            return
        session.remove_items(items)
        self.re_expand_tree()

    @pyqtSlot()
    def on_actionReorder_triggered(self):
        dlg = QDialog(self)
        loadUi(dlg, 'reorder.ui')
        data2obj = dlg.itemList.populate()
        if not dlg.exec_():
            return
        new_structure = []
        for i in range(dlg.itemList.count()):
            new_index = dlg.itemList.item(i).type()
            obj = data2obj[new_index]
            if isinstance(obj, ItemGroup):
                new_structure.append((obj, []))
            else:
                if not new_structure:
                    QMessageBox.warning(
                        self, 'ufit', 'Reordering invalid: every data item '
                        'must be below a group')
                    return
                new_structure[-1][1].append(obj)
        session.reorder_groups(new_structure)
        self.re_expand_tree()

    def selected_items(self, itemcls=SessionItem):
        """Return a list of selected items that belong to the given class."""
        items = (index.internalPointer()
                 for index in self.itemTree.selectedIndexes())
        if not itemcls:
            # This will also return ItemGroup objects!
            return list(items)
        return [item for item in items if isinstance(item, itemcls)]

    def on_itemTree_newSelection(self):
        items = self.selected_items(None)
        if len(items) == 1 and isinstance(items[0], ItemGroup):
            # a group is selected -- act as if we selected all items
            items = items[0].items
        if len(items) == 0:
            self.on_loadBtn_clicked()
        elif len(items) == 1:
            item = items[0]
            if item not in self.itempanels:
                panel = self.itempanels[item] = \
                    item.create_panel(self, self.canvas)
                self.stacker.addWidget(panel)
            else:
                panel = self.itempanels[item]
            self.select_new_panel(panel)
            panel.plot(panel.get_saved_limits())
            self.toolbar.update()
            if self.inspector_window and isinstance(item, ScanDataItem):
                self.inspector_window.setDataset(item.data)
        else:
            paneltype = type(items[0])
            if not hasattr(paneltype, 'create_multi_panel'):
                return
            if paneltype not in self.multipanels:
                panel = self.multipanels[paneltype] = \
                    items[0].create_multi_panel(self, self.canvas)
                self.stacker.addWidget(panel)
            else:
                panel = self.multipanels[paneltype]
            panel.initialize(items)
            self.select_new_panel(panel)
            panel.plot()

    def on_itemTree_expanded(self, index):
        index.internalPointer().expanded = True
        session.set_dirty()

    def on_itemTree_collapsed(self, index):
        index.internalPointer().expanded = False
        session.set_dirty()

    def re_expand_tree(self):
        # expand / collapse all groups according to last saved state
        for group in session.groups:
            if group.expanded:
                self.itemTree.expand(self.itemlistmodel.index_for_group(group))
            else:
                self.itemTree.collapse(
                    self.itemlistmodel.index_for_group(group))

    @pyqtSlot()
    def on_actionInspector_triggered(self):
        if self.inspector_window:
            self.inspector_window.activateWindow()
            return
        self.inspector_window = InspectorWindow(self)

        def deref():
            self.inspector_window = None

        self.inspector_window.replotRequest.connect(
            lambda: self.current_panel.plot(True))
        self.inspector_window.closed.connect(deref)
        if isinstance(self.current_panel, ScanDataPanel):
            self.inspector_window.setDataset(self.current_panel.item.data)
        self.inspector_window.show()

    @pyqtSlot()
    def on_actionAnnotations_triggered(self):
        if self.annotation_window:
            self.annotation_window.activateWindow()
            return
        self.annotation_window = AnnotationWindow(self)

        def deref():
            self.annotation_window = None

        self.annotation_window.closed.connect(deref)
        self.annotation_window.show()

    @pyqtSlot()
    def on_actionLoadData_triggered(self):
        self.on_loadBtn_clicked()

    def on_actionHideFit_toggled(self, on):
        self.canvas.plotter.no_fits = on
        self.current_panel.plot()

    def on_actionConnectData_toggled(self, on):
        self.canvas.plotter.lines = on
        self.current_panel.plot()

    def on_actionDrawSymbols_toggled(self, on):
        self.canvas.plotter.symbols = on
        self.current_panel.plot()

    def on_actionShowLegend_toggled(self, on):
        self.canvas.plotter.legend = on
        self.current_panel.plot()

    def on_actionDrawGrid_toggled(self, on):
        self.canvas.plotter.grid = on
        self.current_panel.plot()

    def on_actionSmoothImages_toggled(self, on):
        self.canvas.plotter.imgsmoothing = on
        self.current_panel.plot()

    def _get_export_filename(self, filter='ASCII text (*.txt)'):
        initialdir = session.props.get('lastexportdir', session.dirname)
        filename, _ = QFileDialog.getSaveFileName(self,
                                                  'Select export file name',
                                                  initialdir, filter)
        if filename == '':
            return ''
        expfilename = path_to_str(filename)
        session.props.lastexportdir = path.dirname(expfilename)
        return expfilename

    @pyqtSlot()
    def on_actionExportASCII_triggered(self):
        expfilename = self._get_export_filename()
        if expfilename:
            self.current_panel.export_ascii(expfilename)

    @pyqtSlot()
    def on_actionExportFIT_triggered(self):
        expfilename = self._get_export_filename()
        if expfilename:
            self.current_panel.export_fits(expfilename)

    @pyqtSlot()
    def on_actionExportPython_triggered(self):
        expfilename = self._get_export_filename('Python files (*.py)')
        if expfilename:
            self.current_panel.export_python(expfilename)

    @pyqtSlot()
    def on_actionExportParams_triggered(self):
        items = self.selected_items(ScanDataItem)
        dlg = ParamExportDialog(self, items)
        if dlg.exec_() != QDialog.Accepted:
            return
        expfilename = self._get_export_filename()
        if expfilename:
            try:
                dlg.do_export(expfilename)
            except Exception as e:
                logger.exception('While exporting parameters')
                QMessageBox.warning(self, 'Error', 'Could not export '
                                    'parameters: %s' % e)

    @pyqtSlot()
    def on_actionPrint_triggered(self):
        self.canvas.print_()

    @pyqtSlot()
    def on_actionPopOut_triggered(self):
        new_win = QMainWindow()
        canvas = MPLCanvas(new_win)
        toolbar = MPLToolbar(canvas, new_win)
        new_win.addToolBar(toolbar)
        splitter = QSplitter(Qt.Vertical, new_win)
        splitter.addWidget(canvas)
        try:
            from ufit.gui.console import QIPythonWidget
        except ImportError:
            lbl = QLabel(
                'Please install IPython with qtconsole to '
                'activate the console part of this window.', new_win)
            splitter.addWidget(lbl)
        else:
            iw = QIPythonWidget('interactive pylab plotting prompt', new_win)
            iw.pushVariables({'ax': canvas.axes})
            iw.executeCommand('from ufit.lab import *')
            iw.executeCommand('sca(ax)')
            iw.setFocus()
            iw.redrawme.connect(canvas.draw_idle)
            splitter.addWidget(iw)
        new_win.setCentralWidget(splitter)
        self.current_panel.plot(limits=None, canvas=canvas)
        new_win.show()

    @pyqtSlot()
    def on_actionSavePlot_triggered(self):
        self.toolbar.save_figure()

    @pyqtSlot()
    def on_actionUnzoom_triggered(self):
        self.toolbar.home()

    def check_save(self):
        if not self.isWindowModified():  # nothing there to be saved
            return True
        resp = QMessageBox.question(
            self, 'ufit', 'Save current session?\n%s' % session.filename,
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if resp == QMessageBox.Yes:
            return self.save_session()
        elif resp == QMessageBox.No:
            return True
        return False

    @pyqtSlot()
    def on_actionNewSession_triggered(self):
        if not self.check_save():
            return
        session.clear()
        self.on_loadBtn_clicked()

    @pyqtSlot()
    def on_actionLoad_triggered(self):
        if not self.check_save():
            return
        initialdir = session.dirname
        if not initialdir:
            with self.sgroup as settings:
                initialdir = settings.value('loadfiledirectory', '')
        filename, _ = QFileDialog.getOpenFileName(self, 'Select file name',
                                                  initialdir,
                                                  'ufit files (*.ufit)')
        if filename == '':
            return
        self.load_session(path_to_str(filename))

    @pyqtSlot()
    def on_actionInsert_triggered(self):
        initialdir = session.dirname
        if not initialdir:
            with self.sgroup as settings:
                initialdir = settings.value('loadfiledirectory', '')
        filename, _ = QFileDialog.getOpenFileName(self, 'Select file name',
                                                  initialdir,
                                                  'ufit files (*.ufit)')
        if filename == '':
            return
        self.insert_session(path_to_str(filename))

    def load_session(self, filename=None):
        if not filename:
            # Recent files action
            action = self.sender()
            if isinstance(action, QAction):
                if not self.check_save():
                    return
                filename = action.data()
        try:
            session.load(filename)
            with self.sgroup as settings:
                settings.setValue('loadfiledirectory', path.dirname(filename))
        except Exception as err:
            logger.exception('Loading session %r failed' % filename)
            QMessageBox.warning(self, 'Error', 'Loading failed: %s' % err)
        else:
            self.re_expand_tree()
            self.setWindowModified(False)
            # if there are annotations, show the window automatically
            if session.props.get('annotations'):
                self.on_actionAnnotations_triggered()

    def insert_session(self, filename):
        temp_session.load(filename)
        basename = path.basename(filename)
        if basename.endswith('.ufit'):
            basename = basename[:-5]
        # XXX slight HACK
        for group in temp_session.groups:
            session.add_group(group.name + ' (from %s)' % basename)
            for item in group.items:
                session.add_item(item)

    @pyqtSlot()
    def on_actionSave_triggered(self):
        self.save_session()

    @pyqtSlot()
    def on_actionSaveAs_triggered(self):
        self.save_session_as()

    @pyqtSlot()
    def on_actionQExplorer_triggered(self):
        ReciprocalViewer(self)

    def save_session(self):
        if session.filename is None:
            return self.save_session_as()
        try:
            session.save()
        except Exception as err:
            logger.exception('Saving session failed')
            QMessageBox.warning(self, 'Error', 'Saving failed: %s' % err)
            return False
        return True

    def save_session_as(self):
        initialdir = session.dirname
        filename, _ = QFileDialog.getSaveFileName(self, 'Select file name',
                                                  initialdir,
                                                  'ufit files (*.ufit)')
        if filename == '':
            return False
        session.set_filename(path_to_str(filename))
        try:
            session.save()
        except Exception as err:
            logger.exception('Saving session failed')
            QMessageBox.warning(self, 'Error', 'Saving failed: %s' % err)
            return False
        return True

    @pyqtSlot()
    def on_actionMergeData_triggered(self):
        items = self.selected_items(ScanDataItem)
        if len(items) < 2:
            return
        dlg = QDialog(self)
        loadUi(dlg, 'rebin.ui')
        if dlg.exec_():
            try:
                precision = float(dlg.precisionEdit.text())
            except ValueError:
                return
            datalist = [i.data for i in items]
            new_data = datalist[0].merge(precision, *datalist[1:])
            session.add_item(ScanDataItem(new_data), self.items[-1].group)

    @pyqtSlot()
    def on_actionQuit_triggered(self):
        self.close()

    def closeEvent(self, event):
        if not self.check_save():
            event.ignore()
            return
        event.accept()
        with self.sgroup as settings:
            settings.setValue('geometry', self.saveGeometry())
            settings.setValue('windowstate', self.saveState())
            settings.setValue('splitstate', self.splitter.saveState())
            settings.setValue('vsplitstate', self.vsplitter.saveState())
            settings.setValue('recentfiles', self.recent_files)

    @pyqtSlot()
    def on_actionAbout_triggered(self):
        dlg = QDialog(self)
        dlg.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
        loadUi(dlg, 'about.ui')
        dlg.lbl.setText(dlg.lbl.text().replace('VERSION', __version__))
        dlg.exec_()

    @pyqtSlot()
    def on_backend_action_triggered(self):
        backends.set_backend(str(self.sender().text()))