Ejemplo n.º 1
0
class UndoAction(ActionController):
    history = RequiredFeature(HistoryController.name)
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def __init__(self):
        ActionController.__init__(self)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setTitle("History").setMenuPath("Edit/Undo").addSupportedData(
            DataType.ANY).addSupportedViewer(ViewerType.ANY).setShortcut(QtGui.QKeySequence("Ctrl+Z"))

    def onAction(self):
        id = self.content_ctrl.getCurrentId()
        self.history.undo(id)
Ejemplo n.º 2
0
class HistoryController(Controller):
    viewers = {}
    skip_next_change = False
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def __init__(self):
        self.content_ctrl.subscribeViewerAdded(self.onViewerAdded)
        self.content_ctrl.subscribeViewerClosed(self.onViewerClosed)

    def undo(self, id):
        history = self.viewers[id]
        if len(history) <= 1:
            return None
        del history[-1]
        # change event will be triggered by undo
        self.skip_next_change = True
        self.content_ctrl.setDataModel(copy.deepcopy(history[-1]), id)

    def onViewerClosed(self, viewer_ctrl):
        del self.viewers[viewer_ctrl.v_id]

    def onViewerAdded(self, viewer_ctrl):
        self.viewers[viewer_ctrl.v_id] = [viewer_ctrl.model]
        self.content_ctrl.subscribeDataChanged(viewer_ctrl.v_id,
                                               self.onDataChanged)

    def onDataChanged(self, viewer_ctrl):
        if self.skip_next_change:
            self.skip_next_change = False
            return
        history = self.viewers[viewer_ctrl.v_id]
        if (len(history) > 10):
            del history[0]
        history.append(viewer_ctrl.model)
Ejemplo n.º 3
0
class QueryCallistoActionController(ActionController):
    item_config = ItemConfig().setMenuPath(
        "File/Open Spectrogram/Callisto/Query")
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def onAction(self):
        dlg = QDialog()
        ui = Ui_QueryCallisto()
        ui.setupUi(dlg)

        now = QDateTime.currentDateTimeUtc()
        ui.start_time.setDateTime(now.addSecs(-2 * 60 * 60))
        ui.end_time.setDateTime(now.addSecs(-1.50 * 60 * 60))

        if dlg.exec_():
            start_time = ui.start_time.dateTime().toString(
                QtCore.Qt.ISODate).replace("T", " ")
            end_time = ui.end_time.dateTime().toString(
                QtCore.Qt.ISODate).replace("T", " ")

            executeLongRunningTask(
                CallistoSpectrogram.from_range,
                [ui.instrument.currentText(), start_time, end_time],
                "Downloading", self._openSpectrogram)

    def _openSpectrogram(self, spectrogram):
        viewer_ctrl = CallistoViewerController.fromSpectrogram(spectrogram)
        self.content_ctrl.addViewerController(viewer_ctrl)
Ejemplo n.º 4
0
class ErrorLogTool(ToolController):
    app_ctrl: AppController = RequiredFeature(AppController.__name__)

    def __init__(self):
        ToolController.__init__(self)

        self._view = QtWidgets.QWidget()
        self._ui = Ui_ErrorLog()
        self._ui.setupUi(self._view)

        self._base_excepthook = sys.excepthook
        sys.excepthook = self._exception_hook

    def _exception_hook(self, exctype, value, traceback):
        # Print the error and traceback to console
        self._base_excepthook(exctype, value, traceback)

        # write to ui
        self._ui.log.append("{}: {}".format(datetime.datetime.now(), value))

        # open tool
        self.app_ctrl.openController(self.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath("Help/Error Log").setTitle(
            "Error Log").setOrientation(QtCore.Qt.BottomDockWidgetArea)

    @property
    def view(self) -> QWidget:
        return self._view
Ejemplo n.º 5
0
class SaveProjectAction(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath("File/Save").addSupportedData(
            DataType.ANY).addSupportedViewer(ViewerType.ANY)

    def onAction(self):
        ctrl = self.content_ctrl.getViewerController()
        model = ctrl.model

        if model.path:
            file = model.path
        else:
            file, _ = QtWidgets.QFileDialog.getSaveFileName(
                filter="Solar Viewer Project (*.svp)")
            if not file:
                return
            model.path = file

        wrapper = ProjectSaveWrapper(type(ctrl), model)
        # write to file
        bin_file = open(file, mode="wb")
        pickle.dump(wrapper, bin_file)
        bin_file.close()
Ejemplo n.º 6
0
class MplToolbarController(ToolbarController):
    connection_ctrl: ViewerConnectionController = RequiredFeature(
        ViewerConnectionController.name)

    def __init__(self):
        ToolbarController.__init__(self)

    @property
    def item_config(self) -> ItemConfig:
        return ToolbarConfig().setMenuPath(
            "View/Toolbar/Default").addSupportedViewer(
                ViewerType.MPL).addSupportedData(DataType.ANY)

    def setup(self, toolbar_widget: QtWidgets.QToolBar):
        toolbar_widget.setOrientation(QtCore.Qt.Vertical)

        pan_icon = QtGui.QIcon(":/image/pan.png")
        pan = toolbar_widget.addAction(pan_icon, "Pan")
        pan.setCheckable(True)

        zoom_icon = QtGui.QIcon(":/image/zoom.png")
        zoom = toolbar_widget.addAction(zoom_icon, "Zoom")
        zoom.setCheckable(True)

        reset_icon = QtGui.QIcon(":/image/home.png")
        reset = toolbar_widget.addAction(reset_icon, "Reset")

        def f(checked, a):
            if checked:
                a.setChecked(False)

        pan.triggered.connect(self._onPan)
        zoom.triggered.connect(self._onZoom)
        reset.triggered.connect(self._onReset)

        self.pan_action = pan
        self.zoom_action = zoom

    def onClose(self):
        if self.pan_action.isChecked() or self.zoom_action.isChecked():
            self.connection_ctrl.remove_lock()

    def _onPan(self):
        if not self.pan_action.isChecked():
            self.connection_ctrl.remove_lock()
            return
        pan_lock = _PanLock(self.item_config, self.pan_action)
        self.connection_ctrl.add_lock(pan_lock)

    def _onZoom(self):
        if not self.zoom_action.isChecked():
            self.connection_ctrl.remove_lock()
            return
        zoom_lock = _ZoomLock(self.item_config, self.zoom_action)
        self.connection_ctrl.add_lock(zoom_lock)

    def _onReset(self):
        view = self.content_ctrl.getViewer()
        view.toolbar.home()
Ejemplo n.º 7
0
class SaveImageController(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath("File/Export/Image").addSupportedData(
            DataType.ANY).addSupportedViewer(ViewerType.MPL)

    def onAction(self):
        ui = Ui_SaveImage()
        dlg = QtWidgets.QDialog()
        ui.setupUi(dlg)
        ui.dpi_spin.setEnabled(False)

        view = self.content_ctrl.getViewerController().view
        figure = view.figure
        canvas = view.canvas

        filters, selectedFilter = self.getFilter(canvas)

        def selectFile():
            path = \
                QFileDialog.getSaveFileName(directory=ui.file_path.text(), filter=filters,
                                            initialFilter=selectedFilter)[0]
            if path:
                ui.file_path.setText(path)

        ui.file_select.clicked.connect(selectFile)

        startpath = os.path.expanduser(
            matplotlib.rcParams['savefig.directory'])
        start = os.path.join(startpath, canvas.get_default_filename())
        ui.file_path.setText(start)

        if not dlg.exec_():
            return

        path = ui.file_path.text()
        dpi = ui.dpi_spin.value() if ui.dpi_check.isChecked() else None

        figure.savefig(path,
                       dpi=dpi,
                       transparent=ui.transparent_check.isChecked())

    def getFilter(self, canvas):
        filetypes = canvas.get_supported_filetypes_grouped()
        sorted_filetypes = [(k, v) for k, v in filetypes.items()]
        sorted_filetypes.sort()
        default_filetype = canvas.get_default_filetype()
        filters = []
        selectedFilter = None
        for name, exts in sorted_filetypes:
            exts_list = " ".join(['*.%s' % ext for ext in exts])
            filter = '%s (%s)' % (name, exts_list)
            if default_filetype in exts:
                selectedFilter = filter
            filters.append(filter)
        filters = ';;'.join(filters)
        return filters, selectedFilter
Ejemplo n.º 8
0
class SpectraToolbarController(ToolbarController):
    item_config = ToolbarConfig().addSupportedViewer(ViewerType.ANY).addSupportedData(DataType.SPECTROGRAM).setMenuPath(
        "View/Toolbar/Spectra").setOrientation(QtCore.Qt.TopToolBarArea)

    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def setup(self, toolbar_widget: QtWidgets.QToolBar):
        left_icon = QtGui.QIcon(":/image/double_left.png")
        right_icon = QtGui.QIcon(":/image/double_right.png")
        range_icon = QtGui.QIcon(":/image/range.png")
        add_icon = QtGui.QIcon(":/image/add.png")

        extend_start = toolbar_widget.addAction(left_icon, "Extend Start")
        extend_end = toolbar_widget.addAction(right_icon, "Extend End")
        range = toolbar_widget.addAction(range_icon, "Set Range")
        add = toolbar_widget.addAction(add_icon, "Add File")

        extend_start.triggered.connect(self.onExtendStart)
        extend_end.triggered.connect(self.onExtendEnd)
        range.triggered.connect(self.onSetRange)
        add.triggered.connect(self.onAddFile)

    def onExtendStart(self):
        v_id = self.content_ctrl.getViewerController().v_id
        executeLongRunningTask(self._extendAction, [-15], "Downloading", self.content_ctrl.setDataModel, [v_id])

    def onExtendEnd(self):
        v_id = self.content_ctrl.getViewerController().v_id
        executeLongRunningTask(self._extendAction, [15], "Downloading", self.content_ctrl.setDataModel, [v_id])

    def onSetRange(self):
        v_id = self.content_ctrl.getViewerController().v_id
        model: CallistoModel = self.content_ctrl.getDataModel()
        dlg = _RangeDialog(model.spectrogram.start, model.spectrogram.end)
        if dlg.exec_():
            model_c = copy.deepcopy(model)
            model_c.spectrogram = model.spectrogram.in_interval(dlg.getStartTime(), dlg.getEndTime())
            self.content_ctrl.setDataModel(model_c, v_id)

    def onAddFile(self):
        v_id = self.content_ctrl.getViewerController().v_id
        paths, _ = QtWidgets.QFileDialog.getOpenFileNames(None, caption="Select Extend File",
                                                          filter="FITS files (*.fits; *.fit; *.fts)")
        if paths:
            model: CallistoModel = copy.deepcopy(self.content_ctrl.getDataModel())
            spectra = [CallistoSpectrogram.read(p) for p in paths]
            spectra.append(model.spectrogram)
            model.spectrogram = CallistoSpectrogram.join_many(spectra)
            self.content_ctrl.setDataModel(model, v_id)

    def _extendAction(self, minutes):
        model: CallistoModel = copy.deepcopy(self.content_ctrl.getDataModel())
        model.spectrogram = model.spectrogram.extend(minutes)
        return model

    def onClose(self):
        pass
Ejemplo n.º 9
0
class SaveFitsAction(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath("File/Export/FITS").addSupportedData(
            DataType.MAP).addSupportedViewer(ViewerType.ANY)

    def onAction(self):
        map: GenericMap = self.content_ctrl.getDataModel().map
        saveFits(map)
Ejemplo n.º 10
0
class CreateCompositeMapTool(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath(
            "File/Open SunPy Composite Map/From Active")

    def onAction(self):
        dlg = QtWidgets.QDialog()
        ui = Ui_OpenComposite()
        ui.setupUi(dlg)

        model = QStandardItemModel()
        viewer_ctrls = self.content_ctrl.getViewerControllers(DataType.MAP)
        for v in viewer_ctrls:
            item = QStandardItem("{}: {}".format(v.v_id, v.getTitle()))
            item.setCheckable(True)
            item.v_id = v.v_id
            model.appendRow(item)

        ui.list.setModel(model)

        if not dlg.exec_():
            return
        map_models = self._getSelectedDataModels(model)
        if len(map_models) == 0:
            return

        model = self._createModel(map_models)
        ctrl = CompositeMapViewerController.fromModel(model)
        self.content_ctrl.addViewerController(ctrl)

    def _createModel(self, models):
        # preserve plot settings
        for i, model in enumerate(models):
            settings = {
                "cmap": model.cmap,
                "norm": model.norm,
                "interpolation": model.interpolation,
                "origin": model.origin
            }
            model.map.plot_settings = settings

        comp_model = CompositeMapModel([model.map for model in models])
        return comp_model

    def _getSelectedDataModels(self, model):
        checked_ids = []
        for index in range(model.rowCount()):
            if model.item(index).checkState() == QtCore.Qt.Checked:
                checked_ids.append(model.item(index).v_id)
        models = [self.content_ctrl.getDataModel(v_id) for v_id in checked_ids]
        return models
Ejemplo n.º 11
0
class MPLCoordinatesMixin:
    status_bar_ctrl: StatusBarController = RequiredFeature(StatusBarController.name)

    def __init__(self):
        # add coordinates of mouse courser to status bar
        self.view.canvas.mpl_connect('motion_notify_event', self.onMapMotion)

    def onMapMotion(self, event):
        if event.inaxes:
            message = event.inaxes.format_coord(event.xdata, event.ydata)
            self.status_bar_ctrl.setText(message)
Ejemplo n.º 12
0
class TestIoC(unittest.TestCase):
    feature: Object = RequiredFeature("TEST1")
    method: Object = RequiredFeature("TEST2", HasMethods("required_method"))
    attr: Object = RequiredFeature("TEST3",
                                   HasAttributes("required_attribute"))

    features.Provide("1", "1")
    features.Provide("2", "2")
    features.Provide("3", "3")
    features: List[str] = MatchingFeatures(IsInstanceOf(str))

    def test_provide(self):
        mock = Object()

        features.Provide("TEST1", mock)
        self.feature.test()

        mock.test.assert_called_once()

    def test_has_method(self):
        mock = Object()
        mock.required_method = Mock()

        features.Provide("TEST2", mock)
        self.method.required_method()

        mock.required_method.assert_called_once()

    def test_has_attribute(self):
        mock = Object()
        mock.required_attribute = "TEST3"

        features.Provide("TEST3", mock)

        self.assertEqual(self.attr.required_attribute, "TEST3")

    def test_matching(self):
        self.assertEqual(["1", "2", "3"], self.features)
Ejemplo n.º 13
0
class SNRController(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath(
            "Help/Calculate SNR").addSupportedViewer(
                ViewerType.MPL).addSupportedData(DataType.MAP)

    def onAction(self):
        data = self.content_ctrl.getViewerController().getZoomedData()
        snr = np.mean(data) / np.std(data)
        message = "Estimated SNR: {0:.7}".format(float(snr))
        QtWidgets.QMessageBox.information(None, "SNR", message)
Ejemplo n.º 14
0
class SaveSpectraFitsAction(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath("File/Export/FITS").addSupportedData(
            DataType.SPECTROGRAM).addSupportedViewer(ViewerType.ANY)

    def onAction(self):
        spec: CallistoSpectrogram = self.content_ctrl.getDataModel(
        ).spectrogram
        name, _ = QtWidgets.QFileDialog.getSaveFileName(
            filter=getExtensionString(FileType.FITS.value))
        if name:
            spec.save(name)
Ejemplo n.º 15
0
class ActionManager:
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def __init__(self, action: QtWidgets.QAction):
        self.action = action
        action.triggered.connect(self._onTriggered)
        action.setEnabled(False)

        self.controllers = {}
        self._active_ctrl = None
        self._shortcut = False
        self.content_ctrl.subscribeViewerChanged(self._checkActionSupported)

    def register(self, ctrl: Controller, action, parent):
        self.controllers[ctrl] = action
        self._checkActionSupported(self.content_ctrl.getViewerController())
        self._registerShortcut(ctrl, parent)

    def _registerShortcut(self, ctrl, parent):
        if self._shortcut is not False:
            assert ctrl.item_config.shortcut == self._shortcut, \
                "invalid configuration. different shortcuts for same action encountered."
            return
        if not ctrl.item_config.shortcut:
            self._shortcut = None
        else:
            self._shortcut = ctrl.item_config.shortcut
            QShortcut(
                ctrl.item_config.shortcut, parent,
                lambda: self.action.trigger()
                if self.action.isEnabled() else None)

    def _checkActionSupported(self, vc: ViewerController):
        dt = vc.data_type if vc else None
        vt = vc.viewer_type if vc else None

        enabled = False
        for c in self.controllers.keys():
            if supported(dt, vt, c.item_config.supported_data_types,
                         c.item_config.supported_viewer_types):
                enabled = True
                self._active_ctrl = c
                break

        self.action.setEnabled(enabled)

    def _onTriggered(self, checked):
        self.controllers[self._active_ctrl]()
Ejemplo n.º 16
0
class DataActionController(ActionController):
    """Base class for actions related to the currently viewed data"""
    content_ctrl = RequiredFeature(ContentController.name)

    def onAction(self):
        data_model = self.content_ctrl.getDataModel()
        call_after = lambda result: self.content_ctrl.setDataModel(result)
        executeLongRunningTask(self._action, [data_model], "Action in Progress", call_after)

    def _action(self, data_model):
        data_copy = copy.deepcopy(data_model)
        modified = self.modifyData(data_copy)
        return modified

    @abstractmethod
    def modifyData(self, data_model: DataModel) -> DataModel:
        raise NotImplementedError
Ejemplo n.º 17
0
class OpenProjectAction(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath("File/Open SV Project")

    def onAction(self):
        file, _ = QtWidgets.QFileDialog.getOpenFileName(
            filter="Solar Viewer Project (*.svp)")
        if not file:
            return

        bin_file = open(file, mode="rb")
        wrapper = pickle.load(bin_file)
        ctrl = wrapper.viewer_ctrl_type.fromModel(wrapper.model)
        self.content_ctrl.addViewerController(ctrl)
        bin_file.close()
Ejemplo n.º 18
0
class DialogController(Controller):
    """Base class for dialog items"""
    content_ctrl = RequiredFeature(content_ctrl_name)

    def __init__(self):
        self._dlg_view = QtWidgets.QDialog()
        self._dlg_view.setWindowTitle(self.item_config.title)
        self._dlg_ui = Ui_Dialog()
        self._dlg_ui.setupUi(self._dlg_view)

        self.setupContent(self._dlg_ui.content)
        self._dlg_ui.button_box.accepted.connect(self._onOk)
        self._dlg_ui.button_box.rejected.connect(self._onCancel)

    @property
    @abstractmethod
    def item_config(self) -> ItemConfig:
        raise NotImplementedError

    @abstractmethod
    def setupContent(self, content_widget):
        raise NotImplementedError

    @abstractmethod
    def onDataChanged(self, viewer_ctrl: ViewerController):
        raise NotImplementedError

    @abstractmethod
    def modifyData(self, data_model: DataModel) -> DataModel:
        raise NotImplementedError

    @property
    def view(self) -> QtWidgets.QDialog:
        viewer_ctrl = self.content_ctrl.getViewerController()
        self.onDataChanged(viewer_ctrl)
        return self._dlg_view

    def _onOk(self):
        viewer_ctrl = self.content_ctrl.getViewerController()
        model = self.modifyData(copy.deepcopy(viewer_ctrl.model))
        self.content_ctrl.setDataModel(model)

    def _onCancel(self):
        pass
Ejemplo n.º 19
0
class CutController(ActionController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def onAction(self):
        viewer_ctrl = self.content_ctrl.getViewerController()
        call_after = lambda result: self.content_ctrl.setDataModel(result)
        executeLongRunningTask(self._action, [viewer_ctrl], "Executing Action",
                               call_after)

    def _action(self, viewer_ctrl):
        submap = viewer_ctrl.getZoomedMap()
        data_copy = copy.deepcopy(viewer_ctrl.model)
        data_copy.map = submap
        return data_copy

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath(
            "Edit/Crop To Current View").addSupportedViewer(
                ViewerType.MPL).addSupportedData(DataType.MAP)
Ejemplo n.º 20
0
class ViewerToolController(ToolController, ConnectionMixin):
    """Base class for viewer aware tool controllers"""
    connection_ctrl: ViewerConnectionController = RequiredFeature(
        ViewerConnectionController.name)

    def __init__(self):
        ToolController.__init__(self)

        self._tool_view = QWidget()
        self._tool_ui = Ui_ViewerTool()
        self._tool_ui.setupUi(self._tool_view)

        self._sub_id = None

        self.setupContent(self._tool_ui.content)
        self._tool_ui.content.resizeEvent = lambda evt: self._tool_ui.scrollArea.setMinimumWidth(
            self._tool_ui.content.sizeHint().width() + self._tool_ui.scrollArea
            .verticalScrollBar().sizeHint().width())

    def supports(self, viewer_ctrl: ViewerController) -> bool:
        return viewer_ctrl is not None and supported(
            viewer_ctrl.data_type, viewer_ctrl.viewer_type,
            self.item_config.supported_data_types,
            self.item_config.supported_viewer_types)

    def enabled(self, value: bool):
        self._tool_view.setEnabled(value)

    @abstractmethod
    def setupContent(self, content_widget):
        raise NotImplementedError

    @property
    def view(self) -> QtWidgets.QWidget:
        self._sub_id = self.connection_ctrl.subscribe(self)

        self._tool_view.closeEvent = self._onClose
        return self._tool_view

    def _onClose(self, *args):
        self.connection_ctrl.unsubscribe(self._sub_id)
Ejemplo n.º 21
0
class ViewerConnectionController(Controller):
    """
    Main controller for connection handling to the active viewer.
    """
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def __init__(self):
        self.subscribers: Dict[int, ConnectionMixin] = {}
        self.connections = {}

        self.sub_vc_id = None
        self.sub_id = 0

        self.lock: ViewerLock = None
        self.active_id = -1
        self.active_sub: ConnectionMixin = None

        self.content_ctrl.subscribeViewerChanged(self._connect)
        self.content_ctrl.subscribeViewerClosed(self._closed)

    def subscribe(self, sub: ConnectionMixin):
        """
        Subscribe a controller to the connection managing

        :param sub: The subscribed controller
        :return: The unique connection id
        """
        self.sub_id += 1
        self.subscribers[self.sub_id] = sub

        if self.lock:
            self.lock.release()
            self.lock = None
        self._connect(self.content_ctrl.getViewerController())

        return self.sub_id

    def unsubscribe(self, s_id):
        """
        Removes the subscription from the connection managing and
        stops active connections of this subscription.

        :param s_id: The unique connection id
        :return: None
        """
        self.subscribers.pop(s_id)
        self._connect(self.content_ctrl.getViewerController(self.active_id))

    def add_lock(self, lock: ViewerLock):
        """
        Adds a viewer lock. This disconnects currently active lock.

        :param lock: The viewer lock implementation
        :return: None
        """
        if self.lock:
            self.lock.release()
        self.lock = lock
        self._connect(self.content_ctrl.getViewerController())

    def remove_lock(self):
        """
        Removes the active viewer lock.

        :return: None
        """
        if self.lock:
            self.lock.release()
        self.lock = None
        self._connect(self.content_ctrl.getViewerController())

    def _connect(self, viewer_ctrl: ViewerController):
        self._disconnectActive()
        self.active_id = viewer_ctrl.v_id if viewer_ctrl else -1
        if self.active_id != -1:
            self.active_sub = self._getFirstSupported(viewer_ctrl)
            if self.active_sub:
                self.active_sub.connect(viewer_ctrl)
        self._setEnabled()

    def _closed(self, viewer_ctrl: ViewerController):
        if not self.active_id == viewer_ctrl.v_id:
            return
        self.active_sub = None

    def _setEnabled(self):
        for sub in self.subscribers.values():
            sub.enabled(sub is self.active_sub)

    def _disconnectActive(self):
        if self.active_sub is None:
            return
        self.active_sub.disconnect(
            self.content_ctrl.getViewerController(self.active_id))
        self.active_sub = None

    def _getFirstSupported(self, viewer_ctrl):
        if self.lock and self.lock.supports(viewer_ctrl):
            return self.lock
        for sub in reversed(list(self.subscribers.values())):
            if sub.supports(viewer_ctrl):
                return sub
        return None
Ejemplo n.º 22
0
class ToolbarController(Controller):
    """Base class for toolbars"""
    content_ctrl: ContentController = RequiredFeature(content_ctrl_name)

    def __init__(self):
        self._sub_id = None
        self._tab_sub_id = None

    @property
    @abstractmethod
    def item_config(self) -> ToolbarConfig:
        raise NotImplementedError

    @abstractmethod
    def setup(self, toolbar_widget: QtWidgets.QToolBar):
        raise NotImplementedError

    @abstractmethod
    def onClose(self):
        pass

    def supports(self, viewer_ctrl: ViewerController) -> bool:
        return viewer_ctrl is not None and supported(
            viewer_ctrl.data_type, viewer_ctrl.viewer_type,
            self.item_config.supported_data_types,
            self.item_config.supported_viewer_types)

    @property
    def view(self) -> QtWidgets.QToolBar:
        self._toolbar_view = QtWidgets.QToolBar()
        self.setup(self._toolbar_view)

        viewer_ctrl = self.content_ctrl.getViewerController()
        self._onTabChanged(viewer_ctrl)

        self._tab_sub_id = self.content_ctrl.subscribeViewerChanged(
            self._onTabChanged)
        self._toolbar_view.closeEvent = self._onClose
        return self._toolbar_view

    def _onTabChanged(self, viewer_ctrl: ViewerController):
        if self._sub_id is not None:
            self.content_ctrl.unsubscribe(self._sub_id)

        if viewer_ctrl is None:
            self._sub_id = None
        else:
            self._sub_id = self.content_ctrl.subscribeDataChanged(
                viewer_ctrl.v_id, self._onDataChanged)

        self._onDataChanged(viewer_ctrl)

    def _onClose(self, *args):
        self.onClose()
        self.content_ctrl.unsubscribe(self._tab_sub_id)
        if self._sub_id:
            self.content_ctrl.unsubscribe(self._sub_id)

    def _onDataChanged(self, viewer_ctrl):
        if viewer_ctrl is None or not supported(
                viewer_ctrl.data_type, viewer_ctrl.viewer_type,
                self.item_config.supported_data_types,
                self.item_config.supported_viewer_types):
            self._toolbar_view.setEnabled(False)
        else:
            self._toolbar_view.setEnabled(True)
Ejemplo n.º 23
0
class DataToolController(ToolController):
    """Base class for tool items with relation to the currently viewed data"""
    content_ctrl: ContentController = RequiredFeature(content_ctrl_name)

    def __init__(self):
        ToolController.__init__(self)

        self._tool_view = QWidget()
        self._tool_ui = Ui_DataTool()
        self._tool_ui.setupUi(self._tool_view)

        self._sub_id = None
        self._tab_sub_id = None
        self._v_id = None

        self.setupContent(self._tool_ui.content)
        self._tool_ui.content.resizeEvent = lambda evt: self._tool_ui.scrollArea.setMinimumWidth(
            self._tool_ui.content.sizeHint().width() + self._tool_ui.scrollArea
            .verticalScrollBar().sizeHint().width())

        apply_btn = self._tool_ui.button_box.button(QDialogButtonBox.Apply)
        apply_btn.clicked.connect(self._onApply)

    @abstractmethod
    def setupContent(self, content_widget: QWidget):
        raise NotImplementedError

    @abstractmethod
    def onDataChanged(self, viewer_ctrl):
        """Triggered action for data changes (switched tab, modified data)"""
        raise NotImplementedError

    @abstractmethod
    def modifyData(self, data_model: DataModel) -> DataModel:
        """
        Triggered apply action.
        :param data_model: The selected data model
        :return: The modified data model
        """
        raise NotImplementedError

    @property
    def view(self) -> QtWidgets:
        viewer_ctrl = self.content_ctrl.getViewerController()
        self._onTabChanged(viewer_ctrl)

        self._tab_sub_id = self.content_ctrl.subscribeViewerChanged(
            self._onTabChanged)
        self._tool_view.closeEvent = self._onClose
        return self._tool_view

    def _onApply(self):
        if not self._v_id:
            return
        self._tool_ui.message_box.hide()
        self._tool_ui.button_box.setEnabled(False)
        data_model = self.content_ctrl.getDataModel(self._v_id)
        executeTask(self._apply, [data_model], self._onResult)

    def _apply(self, data_model):
        try:
            data_copy = copy.deepcopy(data_model)
            result = self.modifyData(data_copy)
            return result if result else data_copy
        except Exception as ex:
            return ex

    def _onResult(self, result):
        self._tool_ui.button_box.setEnabled(True)
        if isinstance(result, Exception):
            self._tool_ui.message_box.showMessage(str(result))
            return
        self.content_ctrl.setDataModel(result)

    def _onTabChanged(self, viewer_ctrl: ViewerController):
        if self._sub_id is not None:
            self.content_ctrl.unsubscribe(self._sub_id)

        if viewer_ctrl is None:
            self._sub_id = None
            self._v_id = None
        else:
            self._sub_id = self.content_ctrl.subscribeDataChanged(
                viewer_ctrl.v_id, self._handleDataChanged)
            self._v_id = viewer_ctrl.v_id

        self._handleDataChanged(viewer_ctrl)

    def _onClose(self, *args):
        self.content_ctrl.unsubscribe(self._tab_sub_id)
        if self._sub_id:
            self.content_ctrl.unsubscribe(self._sub_id)

    def _handleDataChanged(self, viewer_ctrl):
        if viewer_ctrl is None or not supported(
                viewer_ctrl.data_type, viewer_ctrl.viewer_type,
                self.item_config.supported_data_types,
                self.item_config.supported_viewer_types):
            self._tool_view.setEnabled(False)
        else:
            self._tool_view.setEnabled(True)
            self.onDataChanged(viewer_ctrl)
Ejemplo n.º 24
0
class DialogController(Controller):
    """Base class for dialog items."""
    content_ctrl = RequiredFeature(content_ctrl_name)

    def __init__(self):
        self._dlg_view = QtWidgets.QDialog()
        self._dlg_view.setWindowTitle(self.item_config.title)
        self._dlg_ui = Ui_Dialog()
        self._dlg_ui.setupUi(self._dlg_view)

        self.setupContent(self._dlg_ui.content)
        self._dlg_ui.button_box.accepted.connect(self._onOk)
        self._dlg_ui.button_box.rejected.connect(self._onCancel)

    @property
    @abstractmethod
    def item_config(self) -> ItemConfig:
        """
        Create the Configuration.
        Responsible for the representation in the application.

        :return: the menu item configuration
        :rtype: ItemConfig
        """
        raise NotImplementedError

    @abstractmethod
    def setupContent(self, content_widget):
        """
        Internal basic UI setup function. Use the UI setup file here.

        :param content_widget: The Qt parent widget.
        :type: QWidget
        """
        raise NotImplementedError

    @abstractmethod
    def onDataChanged(self, viewer_ctrl: ViewerController):
        """
        Triggered when a new viewer is selected. Only supported data/viewer types need to be handled.

        :param viewer_ctrl: The new viewer controller.
        :type viewer_ctrl: ViewerController
        """
        raise NotImplementedError

    @abstractmethod
    def modifyData(self, data_model: DataModel) -> DataModel:
        """
        Triggered action on dialog accept. Execute action on data model.

        :param data_model: The data model to modify.
        :type data_model: DataModel
        :return: The modified data model.
        :rtype: DataModel
        """
        raise NotImplementedError

    @property
    def view(self) -> QtWidgets.QDialog:
        """
        Returns the dialog view.

        :return: The dialog widget.
        :rtype: QDialog
        """
        viewer_ctrl = self.content_ctrl.getViewerController()
        self.onDataChanged(viewer_ctrl)
        return self._dlg_view

    def _onOk(self):
        """Triggered ok action"""
        viewer_ctrl = self.content_ctrl.getViewerController()
        model = self.modifyData(copy.deepcopy(viewer_ctrl.model))
        self.content_ctrl.setDataModel(model)

    def _onCancel(self):
        """Triggered cancel action"""
        pass
Ejemplo n.º 25
0
class AppController(QtWidgets.QMainWindow):
    content_ctrl: ContentController = RequiredFeature(content_ctrl_name)
    status_bar_ctrl: StatusBarController = RequiredFeature(
        StatusBarController.name)
    viewers = RequiredFeature(viewers_name)
    tool_ctrls: List[ToolController] = MatchingFeatures(
        IsInstanceOf(ToolController))
    dlg_ctrls: List[DialogController] = MatchingFeatures(
        IsInstanceOf(DialogController))
    action_ctrls: List[ActionController] = MatchingFeatures(
        IsInstanceOf(ActionController))
    toolbar_ctrls: List[ToolbarController] = MatchingFeatures(
        IsInstanceOf(ToolbarController))

    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.active_tools = {}
        self.active_toolbars = {}

        self.ui.horizontalLayout.addWidget(self.content_ctrl.view)

        self._initIcon()
        self._initViewers()
        self._initTools()
        self._initDialogs()
        self._initActions()
        self._initToolbars()
        self.setStatusBar(self.status_bar_ctrl.view)

        self.ui.default_toolbar.trigger()
        self.ui.actionQuit.triggered.connect(lambda evt: self.close())

    def openController(self, controller_name: str):
        """
        Opens the controller of the given name in the application
        :param controller_name: the class name of the controller to open (tool, dialog, action or toolbar)
        :return:
        """
        if controller_name in self.active_toolbars or controller_name in self.active_tools:
            return
        for ctrl in self.tool_ctrls:
            if ctrl.name == controller_name:
                self._toggleTool(ctrl)
                return
        for ctrl in self.dlg_ctrls:
            if ctrl.name == controller_name:
                self._openDialog(ctrl)
                return
        for ctrl in self.action_ctrls:
            if ctrl.name == controller_name:
                ctrl.onAction()
                return
        for ctrl in self.toolbar_ctrls:
            if ctrl.name == controller_name:
                self._toggleToolbar(ctrl)
                return

    def _openViewer(self, ctrl: ViewerController):
        extensions = getExtensionString(ctrl.viewer_config.file_types)
        files, _ = QFileDialog.getOpenFileNames(None, "Open File", "",
                                                extensions)
        if not files:
            return
        if ctrl.viewer_config.multi_file:
            viewer = ctrl.fromFile(files)
            self.content_ctrl.addViewerController(viewer)
            return
        for f in files:
            viewer = ctrl.fromFile(f)
            self.content_ctrl.addViewerController(viewer)

    def _toggleTool(self, ctrl: ToolController, action=None):
        if ctrl.name not in self.active_tools:
            dock = QtWidgets.QDockWidget(ctrl.item_config.title)
            content = ctrl.view
            dock.setWidget(content)
            self._setCloseAction(action, content, dock, ctrl.name)
            self.addDockWidget(ctrl.item_config.orientation, dock)
            self.active_tools[ctrl.name] = dock
        else:
            self.active_tools.pop(ctrl.name).close()

    def _setCloseAction(self, action, content, dock, name):
        def f(evt, a=action, c=content, n=name):
            if a:
                a.setChecked(False)
            self.active_tools.pop(n, None)
            c.close()

        dock.closeEvent = f

    def _toggleToolbar(self, ctrl: ToolbarController):
        if ctrl.name not in self.active_toolbars:
            tool_bar = ctrl.view
            self.addToolBar(QtCore.Qt.RightToolBarArea, tool_bar)
            self.active_toolbars[ctrl.name] = tool_bar
        else:
            self.active_toolbars.pop(ctrl.name).close()

    def _openDialog(self, dlg_ctrl: DialogController):
        dlg = dlg_ctrl.view
        dlg.exec_()

    def _initIcon(self):
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap(":/image/icon.png"), QtGui.QIcon.Normal,
                       QtGui.QIcon.Off)
        self.setWindowIcon(icon)

    def _initViewers(self):
        for v_ctrl in self.viewers:
            tree = v_ctrl.viewer_config.menu_path.split("/")
            if len(tree) == 1:
                continue
            action = InitUtil.getAction(tree, self.ui.menubar)
            action.triggered.connect(
                lambda evt, c=v_ctrl: installMissingAndExecute(
                    c.viewer_config.required_pkg, self._openViewer, [c]))

    def _initTools(self):
        for ctrl in self.tool_ctrls:
            tree = ctrl.item_config.menu_path.split("/")
            if len(tree) == 1:
                continue
            action = InitUtil.getAction(tree, self.ui.menubar, True)
            action.triggered.connect(
                lambda evt, c=ctrl, a=action: self._toggleTool(c, a))
            if ctrl.item_config.shortcut:
                QShortcut(
                    ctrl.item_config.shortcut, self, lambda: action.trigger
                    if action.isEnabled() else None)

    def _initDialogs(self):
        for ctrl in self.dlg_ctrls:
            tree = ctrl.item_config.menu_path.split("/")
            if len(tree) == 1:
                continue
            action = InitUtil.getAction(tree, self.ui.menubar)
            action.triggered.connect(lambda evt, c=ctrl: self._openDialog(c))
            self._subscribeItemSupportCheck(action, ctrl)
            if ctrl.item_config.shortcut:
                QShortcut(ctrl.item_config.shortcut,
                          self,
                          lambda a=action: a.trigger()
                          if action.isEnabled() else None)

    def _initActions(self):
        for ctrl in self.action_ctrls:
            tree = ctrl.item_config.menu_path.split("/")
            if len(tree) == 1:
                continue
            action = InitUtil.getAction(tree, self.ui.menubar)
            action.triggered.connect(ctrl.onAction)
            self._subscribeItemSupportCheck(action, ctrl)
            if ctrl.item_config.shortcut:
                QShortcut(ctrl.item_config.shortcut,
                          self,
                          lambda a=action: a.trigger()
                          if action.isEnabled() else None)

    def _initToolbars(self):
        for ctrl in self.toolbar_ctrls:
            tree = ctrl.item_config.menu_path.split("/")
            if len(tree) == 1:
                continue
            action = InitUtil.getAction(tree, self.ui.menubar, checkable=True)
            action.triggered.connect(
                lambda checked, c=ctrl: self._toggleToolbar(c))

    def _subscribeItemSupportCheck(self, action, ctrl):
        def f(vc: ViewerController, a=action, c=ctrl):
            dt = vc.data_type if vc else None
            vt = vc.viewer_type if vc else None
            enabled = supported(dt, vt, c.item_config.supported_data_types,
                                c.item_config.supported_viewer_types)
            a.setEnabled(enabled)

        self.content_ctrl.subscribeViewerChanged(f)
        f(None)  # initial
Ejemplo n.º 26
0
class DataManagerController(ToolController):
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def __init__(self):
        self._view = QtWidgets.QWidget()
        self._ui = Ui_DataManager()
        self._ui.setupUi(self._view)

        self._ui.refresh_button.setIcon(self._view.style().standardIcon(
            QStyle.SP_BrowserReload))
        self._ui.remove_button.setIcon(self._view.style().standardIcon(
            QStyle.SP_DialogNoButton))
        self._ui.add_button.setIcon(self._view.style().standardIcon(
            QStyle.SP_DialogYesButton))

        self.dlg = DataManagerFilterDialog()

        db = QtSql.QSqlDatabase.addDatabase('QSQLITE')
        db.setDatabaseName(
            sunpy.config.get("database", "url").replace("sqlite:///", ""))

        model = QtSql.QSqlTableModel()
        model.setTable("data")
        model.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange)
        model.select()
        self._ui.data_table.setModel(model)

        self.initTableHeader(model)

        self._ui.add_button.clicked.connect(lambda x: self.onAdd())
        self._ui.remove_button.clicked.connect(lambda x: self.onRemove())
        self._ui.open_button.clicked.connect(lambda x: self.onOpen())
        self._ui.refresh_button.clicked.connect(lambda x: self.model.select())
        self._ui.filter_button.clicked.connect(lambda x: self.onFilter())

        self.sunpy_db = Database()
        self.model = model

    def initTableHeader(self, model):
        header = self._ui.data_table.horizontalHeader()
        for i in range(model.columnCount()):
            header.setSectionResizeMode(i,
                                        QtWidgets.QHeaderView.ResizeToContents)
        header.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        header.customContextMenuRequested.connect(self.onHeaderMenu)
        self._ui.data_table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self._ui.data_table.customContextMenuRequested.connect(
            self.onHeaderMenu)

        self._ui.data_table.hideColumn(0)
        self._ui.data_table.hideColumn(2)
        self._ui.data_table.hideColumn(3)
        self._ui.data_table.hideColumn(4)
        self._ui.data_table.hideColumn(8)
        self._ui.data_table.hideColumn(11)
        self._ui.data_table.hideColumn(12)
        self._ui.data_table.hideColumn(13)
        self._ui.data_table.hideColumn(14)

    def onAdd(self):
        paths, _ = QtWidgets.QFileDialog.getOpenFileNames(
            None, filter="FITS files (*.fits; *.fit; *.fts)")
        for p in paths:
            self.sunpy_db.add_from_file(p)
            self.sunpy_db.commit()
        self.model.select()

    def onRemove(self):
        rows = set([i.row() for i in self._ui.data_table.selectedIndexes()])
        for r in rows:
            self.model.removeRow(r)
        self.model.submitAll()
        self.model.select()

    def onOpen(self):
        rows = set([i.row() for i in self._ui.data_table.selectedIndexes()])
        paths = [
            self.model.index(row, self.model.fieldIndex("path")).data()
            for row in rows
        ]
        for path in paths:
            viewer_ctrl = MapViewerController.fromFile(path)
            self.content_ctrl.addViewerController(viewer_ctrl)

    def onFilter(self):
        if self.dlg.exec_():
            self.model.setFilter(self.dlg.getFilter())
            self.model.select()

    def onHeaderMenu(self, point):
        menu = QMenu(self._view)

        actions = []
        for column in range(self.model.columnCount()):
            label = self.model.headerData(column, QtCore.Qt.Horizontal)
            action = QtWidgets.QAction(label)
            action.setCheckable(True)
            action.setChecked(not self._ui.data_table.isColumnHidden(column))
            event = lambda checked, c=column: self._ui.data_table.showColumn(
                c) if checked else self._ui.data_table.hideColumn(c)
            action.triggered.connect(event)
            actions.append(action)
        menu.addActions(actions)

        menu.exec_(QCursor.pos())

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setMenuPath("File/Data Manager").setTitle(
            "Data Manager")

    @property
    def view(self) -> QWidget:
        self.model.select()
        return self._view
Ejemplo n.º 27
0
class EventController(ToolController):  #

    result_ctrl: DownloadResultController = RequiredFeature(DownloadResultController.name)

    def __init__(self):
        self.client = HEKClient()

        self.query_id = 0

        ToolController.__init__(self)
        self._view = QtWidgets.QWidget()
        self._ui = Ui_DownloadEvent()
        self._ui.setupUi(self._view)
        self._ui.message_box.hide()
        self.table = self._ui.table

        self._ui.event_type.addItems([c().item.upper() for c in hek.attrs.EventType.__subclasses__()])

        header = self.table.horizontalHeader()
        for i in range(self.table.columnCount()):
            header.setSectionResizeMode(i, QtWidgets.QHeaderView.ResizeToContents)

        now = QDateTime.currentDateTimeUtc()
        self._ui.from_date.setDateTime(now.addSecs(-2 * 60 * 60))
        self._ui.to_date.setDateTime(now.addSecs(-1.50 * 60 * 60))

        self._ui.search_button.clicked.connect(self._onSearch)
        self._ui.query_button.clicked.connect(self._onQuery)

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setTitle("Event Download Tool").setMenuPath("File/HEK")

    @property
    def view(self) -> QtWidgets:
        return self._view

    def _onSearch(self):
        self._ui.message_box.hide()
        try:
            self._ui.search_button.setEnabled(False)
            self._ui.search_button.setText("Loading...")

            self.table.setRowCount(0)

            start = self._ui.from_date.dateTime().toString(QtCore.Qt.ISODate)
            end = self._ui.to_date.dateTime().toString(QtCore.Qt.ISODate)
            event = self._ui.event_type.currentText()
            attrs = [hek.attrs.Time(start=start, end=end), hek.attrs.EventType(event)]
            executeTask(self.client.search, attrs, self._onSearchResult)
        except Exception as ex:
            self._ui.message_label.setText("Invalid Query: " + str(ex))
            self._ui.message_box.show()

    def _onQuery(self):
        indexes = self.table.selectedIndexes()
        if len(indexes) == 0:
            return

        vso_query = hek2vso.translate_results_to_query(self.query[indexes[0].row()])
        self.result_ctrl.query(vso_query[0])

    def _onSearchResult(self, query):
        self.query = query
        self.table.setRowCount(len(query))
        for index, item in enumerate(query):
            location = self._createLocation(item)
            self.table.setItem(index, 0, QtWidgets.QTableWidgetItem(item["event_type"]))
            self.table.setItem(index, 1, QtWidgets.QTableWidgetItem(item["event_starttime"]))
            self.table.setItem(index, 2, QtWidgets.QTableWidgetItem(item["event_endtime"]))
            self.table.setItem(index, 3, QtWidgets.QTableWidgetItem(location))
            self.table.setItem(index, 4, QtWidgets.QTableWidgetItem(item["obs_observatory"]))
            self.table.setItem(index, 5, QtWidgets.QTableWidgetItem(item["obs_instrument"]))
            self.table.setItem(index, 6, QtWidgets.QTableWidgetItem(item["obs_channelid"]))
            self.table.setItem(index, 7, QtWidgets.QTableWidgetItem(item["frm_name"]))
        self._ui.search_button.setEnabled(True)
        self._ui.search_button.setText("Search")

    def _createLocation(self, item):
        event_coordunit = item["event_coordunit"]
        if not event_coordunit:
            return ""
        location = "( "
        if item["event_coord1"] is not None:
            location += str(item["event_coord1"])
        if item["event_coord2"] is not None:
            location += ", " + str(item["event_coord2"])
        if item["event_coord3"] is not None:
            location += ", " + str(item["event_coord3"])
        location += " ) " + event_coordunit
        return location
Ejemplo n.º 28
0
class DownloadResultController(ToolController):
    queries = {}
    app_ctrl: AppController = RequiredFeature(AppController.__name__)
    content_ctrl: ContentController = RequiredFeature(ContentController.name)

    def __init__(self):
        self._view = QtWidgets.QWidget()
        self._ui = Ui_DownloadResult()
        self._ui.setupUi(self._view)

        self._ui.tabs.clear()
        self._ui.tabs.tabCloseRequested.connect(self._onRemoveTab)

        self.database = Database()

        self.tabs = {}
        self.queries = {}
        self.query_id = 0
        self.loading = []
        self.loaded = {
            entry.fileid: entry.path
            for entry in list(self.database)
        }

        self._ui.download_button.clicked.connect(
            lambda evt: self._onDownloadSelected())
        self._ui.open_button.clicked.connect(
            lambda evt: self._onOpenSelected())

    def query(self, attrs):
        # open tool if not already opened
        self.app_ctrl.openController(self.name)

        self.query_id += 1

        # add pending tab
        tab = ResultTab(self.query_id)
        self.tabs[self.query_id] = tab
        index = self._ui.tabs.addTab(tab, "Query " + str(self.query_id))
        self._ui.tabs.setCurrentIndex(index)
        # start query
        executeTask(Fido.search, attrs, self._onQueryResult, [self.query_id])
        # register events
        tab.download.connect(
            lambda f_id, q_id=self.query_id: self.download(q_id, f_id))
        tab.open.connect(lambda f_id: self._onOpen(f_id))

    def _onQueryResult(self, query, id):
        if id not in self.tabs:
            return
        query_model = self._convertQuery(query)
        self.tabs[id].loadQuery(query_model)
        self.tabs[id].setLoading(self.loading)
        self.tabs[id].setLoaded(self.loaded.keys())
        self.queries[id] = query

    def download(self, q_id, f_id):
        req = copy.copy(self.queries[q_id])
        req._list = [copy.copy(r) for r in req]
        for resp in req:
            resp[:] = [item for item in resp if item.fileid == f_id]

        self._addLoading([f_id])
        executeTask(Fido.fetch, [req], self._onDownloadResult, [f_id, req])

    def _onDownloadResult(self, paths, f_id, request):
        path = paths[0]
        entry = list(
            tables.entries_from_fido_search_result(
                request, self.database.default_waveunit))[0]
        entry.path = path
        self.database.add(entry)
        self.database.commit()
        self._addLoaded({f_id: path})

    def _onOpen(self, f_id):
        viewer = MapViewerController.fromFile(self.loaded[f_id])
        self.content_ctrl.addViewerController(viewer)

    def _onRemoveTab(self, index):
        tab = self._ui.tabs.widget(index)
        self._ui.tabs.removeTab(index)
        self.tabs.pop(tab.q_id)

    def _onDownloadSelected(self):
        tab = self._ui.tabs.currentWidget()
        f_ids = tab.getSelectedFIds()
        for f_id in f_ids:
            if f_id in self.loading or f_id in self.loaded:
                continue
            self.download(tab.q_id, f_id)

    def _onOpenSelected(self):
        tab = self._ui.tabs.currentWidget()
        f_ids = tab.getSelectedFIds()
        for f_id in f_ids:
            if f_id not in self.loaded:
                continue
            self._onOpen(f_id)

    def _convertQuery(self, query):
        items = [item for response in query for item in response]
        return [[c[1](item) for c in columns] for item in items]

    def _addLoading(self, f_ids):
        self.loading.extend(f_ids)
        for tab in self.tabs.values():
            tab.setLoading(f_ids)

    def _addLoaded(self, dict):
        self.loading = [
            f_id for f_id in self.loading if f_id not in dict.keys()
        ]
        self.loaded.update(dict)
        for tab in self.tabs.values():
            tab.setLoaded(dict.keys())

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setTitle("Download Results").setOrientation(
            QtCore.Qt.BottomDockWidgetArea)

    @property
    def view(self) -> QtWidgets:
        return self._view
Ejemplo n.º 29
0
class DownloadController(ToolController):
    result_ctrl: DownloadResultController = RequiredFeature(
        DownloadResultController.name)

    def __init__(self):
        self.query_id = 0

        ToolController.__init__(self)
        self._view = QtWidgets.QWidget()
        self._ui = Ui_Download()
        self._ui.setupUi(self._view)
        self._ui.content.resizeEvent = lambda evt: self._ui.scrollArea.setMinimumWidth(
            self._ui.content.sizeHint().width(
            ) + self._ui.scrollArea.verticalScrollBar().sizeHint().width())
        self._ui.content_layout.setAlignment(QtCore.Qt.AlignTop)

        self.possible_filters = [e.value for e in Filter]
        self.refreshActiveFilters()
        self.active_filters = []
        self.addMandatoryFilters()

        self._ui.add_filter_button.clicked.connect(self.onAddFilter)
        self._ui.query_button.clicked.connect(self.onQuery)

    def refreshActiveFilters(self):
        self._ui.filter_combo.clear()
        self._ui.filter_combo.addItems(
            [f["label"] for f in self.possible_filters])

    def addMandatoryFilters(self):
        mandatory_filters = [
            e for e in self.possible_filters if e["mandatory"]
        ]
        for f in mandatory_filters:
            self.addFilter(f)

    def addFilter(self, f):
        filter_box = _FilterBox(f)
        filter_box.closeEvent = lambda evt, fi=filter_box: self.onFilterDestroyed(
            fi)
        self._ui.content_layout.addWidget(filter_box)
        self.possible_filters.remove(f)
        self.active_filters.append(f)
        self.refreshActiveFilters()

    def onAddFilter(self, event):
        selection = self._ui.filter_combo.currentText()
        selected_filter = [
            f for f in self.possible_filters if f["label"] == selection
        ]
        if len(selected_filter) == 1:
            self.addFilter(selected_filter[0])

    def onQuery(self, *args):
        self._ui.message_box.hide()
        try:
            attrs = []
            filters = [
                f for f in self._ui.content.children()
                if isinstance(f, _FilterBox)
            ]
            for f in filters:
                attrs.append(f.value())
            self.result_ctrl.query(attrs)
        except Exception as ex:
            self._ui.message_box.showMessage("Invalid Query: " + str(ex))

    def onFilterDestroyed(self, filter_panel):
        filter = filter_panel.filter
        self.possible_filters.append(filter)
        self.active_filters.remove(filter)
        self.refreshActiveFilters()

    @property
    def item_config(self) -> ItemConfig:
        return ItemConfig().setTitle("Download Tool").setMenuPath(
            "File/Download Data")

    @property
    def view(self) -> QtWidgets:
        return self._view