Source code for vdat.gui.tasks

# Virus Data Analysis Tool: a data reduction GUI for HETDEX/VIRUS data
# Copyright (C) 2016, 2017  "The HETDEX collaboration"
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

from qtpy import QtCore, QtWidgets
import six

import vdat.config as vconf


[docs]class TaskError(Exception): pass
[docs]class VDATStack(QtWidgets.QStackedWidget): """Parent widget containing the buttons to run the reduction steps Parameters ---------- parent : :class:`QtWidget` instance The QtWidget that the menu is attached to name : string name of the widget Attributes ---------- """ def __init__(self, cls, parent=None, name="tasks_stack"): super(VDATStack, self).__init__(parent=parent) # map each of the sub widget containing groups of buttons self._subwidgets_map = {} self.setObjectName(name) # create an empty widget to hide the other ones when nothing has been # selected self.blank_widget = QtWidgets.QWidget(parent=self) self.blank_widget.setObjectName('blank') blank_index = self.addWidget(self.blank_widget) self.setCurrentIndex(blank_index) if cls is None: raise Exception('Class to be stacked must be specified!') self.cls = cls
[docs] def get_subwidget(self, name): """Returns a sub-widget called ``name``. Create it if needed""" try: return self._subwidgets_map[name], False except KeyError: subwidget = self.cls(parent=self) self._subwidgets_map[name] = subwidget self.addWidget(subwidget) return subwidget, True
[docs] def setCurrentWidgetByName(self, name): """Set the current widget by name Parameters ---------- name : string name associated with the widget """ if name in self._subwidgets_map: widget = self._subwidgets_map[name] else: widget = self.blank_widget super(VDATStack, self).setCurrentWidget(widget) return widget
[docs]class VDATTaskStack(VDATStack): """Parent widget containing the buttons to run the reduction steps Parameters ---------- parent : :class:`QtWidget` instance The QtWidget that the menu is attached to name : string name of the widget Attributes ---------- """ sig_taskChanged = QtCore.Signal(str, dict) sig_runCommand = QtCore.Signal(str, list) def __init__(self, parent=None, name="tasks_stack"): super(VDATTaskStack, self).__init__(VDATTaskWidget, parent=parent, name=name)
[docs] def get_subwidget(self, name): """Returns a sub-widget called ``name``. Create it if needed""" subwidget, new = super(VDATTaskStack, self).get_subwidget(name) if new: subwidget.sig_selected.connect(self.taskSelected) subwidget.sig_command.connect(self.commandTriggered) return subwidget
[docs] def setCurrentWidgetByName(self, name): """Set the current widget by name Parameters ---------- name : string name associated with the widget """ try: widget = super(VDATTaskStack, self).setCurrentWidgetByName(name) widget.selectFirst() except AttributeError: raise TaskError('No task defined!')
[docs] @QtCore.Slot(str, dict) def taskSelected(self, s, d): self.sig_taskChanged.emit(s, d)
[docs] @QtCore.Slot(str, list) def commandTriggered(self, s, l): self.sig_runCommand.emit(s, l)
[docs]class VDATButtonStack(VDATStack): """Parent widget containing the buttons to run the reduction steps Parameters ---------- parent : :class:`QtWidget` instance The QtWidget that the menu is attached to name : string name of the widget Attributes ---------- """ sig_runCommand = QtCore.Signal(int, list) def __init__(self, parent=None, name="tasks_stack"): super(VDATButtonStack, self).__init__(VDATButtonWidget, parent=parent, name=name)
[docs] def get_subwidget(self, name): """Returns a sub-widget called ``name``. Create it if needed""" subwidget, new = super(VDATButtonStack, self).get_subwidget(name) if new: subwidget.triggered.connect(self.commandTriggered) return subwidget
[docs] @QtCore.Slot(str, list) def commandTriggered(self, s, l): self.sig_runCommand.emit(s, l)
[docs]class VDATTaskWidget(QtWidgets.QWidget): sig_selected = QtCore.Signal(str, dict) sig_command = QtCore.Signal(str, list) def __init__(self, **kwargs): super(VDATTaskWidget, self).__init__(**kwargs) self.buttongroup = QtWidgets.QButtonGroup(parent=self) self.buttongroup.setExclusive(True) self.widget = QtWidgets.QWidget() self.blayout = QtWidgets.QVBoxLayout(self) self.blayout.setSpacing(0) self.blayout.setContentsMargins(0, 0, 0, 0) self.widget.setLayout(self.blayout) self.scrollarea = QtWidgets.QScrollArea() self.scrollarea.setWidgetResizable(True) self.scrollarea.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarAlwaysOff) self.scrollarea.setWidget(self.widget) self.btnStack = VDATStack(VDATButtonWidget, self) self.layout = QtWidgets.QVBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self.scrollarea) self.layout.setStretchFactor(self.scrollarea, 10) self.layout.addWidget(self.btnStack) self.layout.setStretchFactor(self.btnStack, 1) self.setLayout(self.layout) self.buttongroup.buttonClicked[int].connect(self.buttonSelected) self._tasks = {} self._task_dicts = {} self.maxID = 1
[docs] def selectFirst(self): self.buttongroup.button(1).click()
[docs] def add_task(self, task_dict, tool_tip=None): """Adds a button to the widget. Parameters ---------- name : string name of the button commands : list of strings, optional strings defining the commands tool_tip : string, optional text that appears as a tool tip when the user hovers their mouse over the button """ name = task_dict['step_name'] btn = QtWidgets.QPushButton(name, parent=self) btn.setFlat(True) btn.setCheckable(True) if tool_tip is not None: btn.setToolTip(tool_tip) self.blayout.addWidget(btn) self.buttongroup.addButton(btn, self.maxID) self._tasks[self.maxID] = name self._task_dicts[self.maxID] = task_dict line = QtWidgets.QFrame(parent=self) line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) self.blayout.addWidget(line) bstk = None if 'buttons' in task_dict: bstk, new = self.btnStack.get_subwidget(name) for b in task_dict['buttons']: tip = None if 'tool_tip' in b: tip = b['tool_tip'] if new: btn = bstk.add_button(b['name'], b['commands'], tool_tip=tip) bstk.sig_runCommand.connect(self.commandTriggered) self.maxID += 1 return btn, bstk
[docs] def add_stretch(self, stretch=1): """Add a stretch to the layout containing the buttons Parameters ---------- stretch : int, optional stretch factor """ # Add a strech to keep buttons on top self.blayout.addStretch()
[docs] @QtCore.Slot(int) def buttonSelected(self, i): ''' Slot to catch the the button pressed from the button group, and re-emit with updated information ''' self.btnStack.setCurrentWidgetByName(self._tasks[i]) self.sig_selected.emit(self._tasks[i], self._task_dicts[i])
[docs] @QtCore.Slot(str, list) def commandTriggered(self, s, l): ''' Slot to catch the button pressed from the button group, and re-emit with updated information ''' self.sig_command.emit(s, l)
[docs]class VDATButtonWidget(QtWidgets.QWidget): sig_runCommand = QtCore.Signal(str, list) def __init__(self, **kwargs): super(VDATButtonWidget, self).__init__(**kwargs) self.buttongroup = QtWidgets.QButtonGroup(parent=self) self.layout = QtWidgets.QVBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.layout) self.buttongroup.buttonClicked[int].connect(self.clicked) self._cmds = {} self._desc = {} self.maxID = 1
[docs] def add_button(self, name, command, tool_tip=None): """Adds a button to the widget. Parameters ---------- name : string name of the button commands : list of strings, optional strings defining the commands tool_tip : string, optional text that appears as a tool tip when the user hovers their mouse over the button """ if isinstance(command, six.string_types): command = [command, ] btn = QtWidgets.QPushButton(name, parent=self) if tool_tip is not None: btn.setToolTip(tool_tip) self.layout.addWidget(btn) self.buttongroup.addButton(btn, self.maxID) self._desc[self.maxID] = name self._cmds[self.maxID] = command self.maxID += 1 return btn
[docs] @QtCore.Slot(int) def clicked(self, i): self.sig_runCommand.emit(self._desc[i], self._cmds[i])
[docs]def setup_tasks(parent=None): """Set up and return the buttons Parameters ---------- parent : :class:`PyQt5.QtWidgets.QWidget` parent of the button widget Returns ------- :class:`ButtonsMenu` button widgets container """ task_config = vconf.get_config('tasks') tasks = VDATTaskStack(parent) # loop through the entries in the configuration file for k, v in six.iteritems(task_config): t_widget = tasks.get_subwidget(k) # the value ``v`` is a list of buttons for step in v: # btns = None # if 'buttons' in step: # btns = step['buttons'] b, c = t_widget.add_task(step) # b.clicked.connect(t_widget.) # Push the buttons to the top of the layout t_widget.add_stretch() return tasks