コード例 #1
0
 def refresh_profiles(self, list_widget: QListWidget,
                      new_values: typing.List[str]):
     index = self.get_index(list_widget.currentItem(), new_values)
     list_widget.clear()
     list_widget.addItems(new_values)
     if index != -1:
         list_widget.setCurrentRow(index)
コード例 #2
0
ファイル: caselist.py プロジェクト: oyvindeide/ert
class CaseList(QWidget):
    def __init__(self, facade: LibresFacade, notifier: ErtNotifier):
        self.facade = facade
        self.notifier = notifier
        QWidget.__init__(self)

        addHelpToWidget(self, "init/case_list")

        layout = QVBoxLayout()

        self._list = QListWidget(self)
        self._list.setMinimumHeight(100)
        self._list.setMaximumHeight(250)
        self._default_selection_mode = self._list.selectionMode()
        self.setSelectable(False)

        layout.addWidget(QLabel("Available cases:"))
        layout.addWidget(self._list)

        self._addRemoveWidget = AddRemoveWidget(self.addItem,
                                                self.removeItem,
                                                horizontal=True)
        self._addRemoveWidget.enableRemoveButton(False)
        layout.addWidget(self._addRemoveWidget)

        self._title = "New keyword"
        self._description = "Enter name of keyword:"

        self.setLayout(layout)

        notifier.ertChanged.connect(self.updateList)
        self.updateList()

    def setSelectable(self, selectable):
        if selectable:
            self._list.setSelectionMode(self._default_selection_mode)
        else:
            self._list.setSelectionMode(QAbstractItemView.NoSelection)

    def addItem(self):
        dialog = ValidatedDialog("New case", "Enter name of new case:",
                                 getAllCases(self.facade))
        new_case_name = dialog.showAndTell()
        if not new_case_name == "":
            self.facade.select_or_create_new_case(new_case_name)
            self.notifier.ertChanged.emit()

    def removeItem(self):
        message = "Support for removal of items has not been implemented!"
        QMessageBox.information(self, "Not implemented!", message)

    def updateList(self):
        """Retrieves data from the model and inserts it into the list"""
        case_list = getAllCases(self.facade)

        self._list.clear()

        for case in case_list:
            self._list.addItem(case)
コード例 #3
0
class HeaderEditDialog(QDialog):

    # name of the current preset; whether to set this preset as default; list of Columns
    header_changed = Signal(str, bool, list)

    def __init__(self, parent, table_header):
        super().__init__(parent)

        self.table_header = table_header
        self.default_preset_name = None
        self.preset_name = table_header.preset_name
        self.columns = deepcopy(table_header.columns)
        self.setupUi()
        self.update_output()

    def setupUi(self):
        self.resize(240, 400)
        self.vbox = QVBoxLayout(self)
        self.presetLabel = QLabel(self)
        self.columnList = QListWidget(self)
        self.setAsDefaultCheckbox = QCheckBox("Set as default preset", self)
        self.vbox.addWidget(self.presetLabel)
        self.vbox.addWidget(self.columnList)
        self.vbox.addWidget(self.setAsDefaultCheckbox)

        self.columnList.setDragDropMode(QListWidget.InternalMove)
        self.columnList.setDefaultDropAction(Qt.MoveAction)
        self.columnList.setSelectionMode(QListWidget.ExtendedSelection)
        self.columnList.setAlternatingRowColors(True)
        self.columnList.installEventFilter(self)
        self.columnList.setContextMenuPolicy(Qt.CustomContextMenu)
        self.columnList.customContextMenuRequested.connect(self.open_menu)
        self.columnList.model().rowsMoved.connect(self.read_columns_from_list)

        # for a dumb qss hack to make selected checkboxes not white on a light theme
        self.columnList.setObjectName("ColumnList")

        buttons = QDialogButtonBox.Reset | QDialogButtonBox.Save | QDialogButtonBox.Cancel
        self.buttonBox = QDialogButtonBox(buttons, self)
        self.vbox.addWidget(self.buttonBox)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)
        self.resetButton = self.buttonBox.button(QDialogButtonBox.Reset)
        self.resetButton.clicked.connect(self.reset_to_stock)

    def eventFilter(self, object, event):
        if event.type() == QEvent.KeyPress:
            if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return:
                self.toggle_selected_columns()
                return True
            if event.key() == Qt.Key_Delete:
                self.delete_selected()
                return True
        return False

    def update_output(self):
        self.presetLabel.setText("Preset: {}".format(self.preset_name))
        self.setAsDefaultCheckbox.setChecked(
            CONFIG['default_header_preset'] == self.preset_name)
        self.columnList.clear()
        for column in self.columns:
            ColumnListItem(self.columnList, column)

    def accept(self):
        self.read_columns_from_list()
        self.header_changed.emit(self.preset_name,
                                 self.setAsDefaultCheckbox.isChecked(),
                                 self.columns)
        self.done(0)

    def reject(self):
        self.done(0)

    def reset_to_stock(self):
        self.columns = deepcopy(DEFAULT_COLUMNS)
        self.update_output()

    def read_columns_from_list(self):
        new_columns = []
        for i in range(self.columnList.count()):
            item = self.columnList.item(i)
            new_columns.append(item.column)
        self.columns = new_columns

    def toggle_selected_columns(self):
        selected = self.columnList.selectedItems()
        for item in selected:
            value_now = item.data(Qt.CheckStateRole)
            item.setData(Qt.CheckStateRole, not value_now)
        self.columnList.reset(
        )  # @Improvement: is there a better way to update QListWidget?

    def open_menu(self, position):
        menu = QMenu(self)

        preset_menu = menu.addMenu('Presets')
        preset_menu.addAction('New preset', self.new_preset_dialog)
        preset_menu.addSeparator()

        preset_names = CONFIG.get_header_presets()

        if len(preset_names) == 0:
            action = preset_menu.addAction('No presets')
            action.setEnabled(False)
        else:
            delete_menu = menu.addMenu('Delete preset')
            for name in preset_names:
                preset_menu.addAction(name, partial(self.load_preset, name))
                delete_menu.addAction(name, partial(self.delete_preset, name))

        menu.addSeparator()
        menu.addAction('New column...', self.create_new_column_dialog)

        if len(self.columnList.selectedIndexes()) > 0:
            menu.addAction('Delete selected', self.delete_selected)

        menu.popup(self.columnList.viewport().mapToGlobal(position))

    def load_preset(self, name):
        new_columns = CONFIG.load_header_preset(name)
        if not new_columns:
            return

        self.columns = new_columns
        self.preset_name = name
        self.update_output()

    def new_preset_dialog(self):
        d = QInputDialog(self)
        d.setLabelText('Enter the new name for the new preset:')
        d.setWindowTitle('Create new preset')
        d.textValueSelected.connect(self.create_new_preset)
        d.open()

    def create_new_preset(self, name):
        if name in CONFIG.get_header_presets():
            show_warning_dialog(
                self, "Preset creation error",
                'Preset named "{}" already exists.'.format(name))
            return
        if len(name.strip()) == 0:
            show_warning_dialog(
                self, "Preset creation error",
                'This preset name is not allowed.'.format(name))
            return

        self.preset_name = name
        self.update_output()
        CONFIG.save_header_preset(name, self.columns)

    def delete_preset(self, name):
        CONFIG.delete_header_preset(name)
        if name == self.preset_name:
            self.columns = deepcopy(DEFAULT_COLUMNS)
            self.update_output()

    def create_new_column_dialog(self):
        d = CreateNewColumnDialog(self)
        d.add_new_column.connect(self.add_new_column)
        d.setWindowTitle('Create new column')
        d.open()

    def add_new_column(self, name, title):
        new_column = Column(name, title)
        # if the last column is message, insert this column before it (I think that makes sense?)
        if len(self.columns) == 0:
            self.columns.append(new_column)
        elif self.columns[-1].name in ('message', 'msg'):
            self.columns.insert(-1, new_column)
        else:
            self.columns.append(new_column)
        self.update_output()

    def delete_selected(self):
        selected = self.columnList.selectedItems()
        for item in selected:
            self.columnList.takeItem(self.columnList.row(item))
        self.read_columns_from_list()
        self.update_output()
コード例 #4
0
ファイル: pathmanager.py プロジェクト: wtheis/spyder
class PathManager(QDialog):
    redirect_stdio = Signal(bool)

    def __init__(self,
                 parent=None,
                 pathlist=None,
                 ro_pathlist=None,
                 not_active_pathlist=None,
                 sync=True):
        QDialog.__init__(self, parent)

        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)

        assert isinstance(pathlist, list)
        self.pathlist = pathlist
        if not_active_pathlist is None:
            not_active_pathlist = []
        self.not_active_pathlist = not_active_pathlist
        if ro_pathlist is None:
            ro_pathlist = []
        self.ro_pathlist = ro_pathlist

        self.last_path = getcwd_or_home()

        self.setWindowTitle(_("PYTHONPATH manager"))
        self.setWindowIcon(ima.icon('pythonpath'))
        self.resize(500, 300)

        self.selection_widgets = []

        layout = QVBoxLayout()
        self.setLayout(layout)

        top_layout = QHBoxLayout()
        layout.addLayout(top_layout)
        self.toolbar_widgets1 = self.setup_top_toolbar(top_layout)

        self.listwidget = QListWidget(self)
        self.listwidget.currentRowChanged.connect(self.refresh)
        self.listwidget.itemChanged.connect(self.update_not_active_pathlist)
        layout.addWidget(self.listwidget)

        bottom_layout = QHBoxLayout()
        layout.addLayout(bottom_layout)
        self.sync_button = None
        self.toolbar_widgets2 = self.setup_bottom_toolbar(bottom_layout, sync)

        # Buttons configuration
        bbox = QDialogButtonBox(QDialogButtonBox.Close)
        bbox.rejected.connect(self.reject)
        bottom_layout.addWidget(bbox)

        self.update_list()
        self.refresh()

    @property
    def active_pathlist(self):
        return [
            path for path in self.pathlist
            if path not in self.not_active_pathlist
        ]

    def _add_widgets_to_layout(self, layout, widgets):
        layout.setAlignment(Qt.AlignLeft)
        for widget in widgets:
            layout.addWidget(widget)

    def setup_top_toolbar(self, layout):
        toolbar = []
        movetop_button = create_toolbutton(
            self,
            text=_("Move to top"),
            icon=ima.icon('2uparrow'),
            triggered=lambda: self.move_to(absolute=0),
            text_beside_icon=True)
        toolbar.append(movetop_button)
        moveup_button = create_toolbutton(
            self,
            text=_("Move up"),
            icon=ima.icon('1uparrow'),
            triggered=lambda: self.move_to(relative=-1),
            text_beside_icon=True)
        toolbar.append(moveup_button)
        movedown_button = create_toolbutton(
            self,
            text=_("Move down"),
            icon=ima.icon('1downarrow'),
            triggered=lambda: self.move_to(relative=1),
            text_beside_icon=True)
        toolbar.append(movedown_button)
        movebottom_button = create_toolbutton(
            self,
            text=_("Move to bottom"),
            icon=ima.icon('2downarrow'),
            triggered=lambda: self.move_to(absolute=1),
            text_beside_icon=True)
        toolbar.append(movebottom_button)
        self.selection_widgets.extend(toolbar)
        self._add_widgets_to_layout(layout, toolbar)
        return toolbar

    def setup_bottom_toolbar(self, layout, sync=True):
        toolbar = []
        add_button = create_toolbutton(self,
                                       text=_('Add path'),
                                       icon=ima.icon('edit_add'),
                                       triggered=self.add_path,
                                       text_beside_icon=True)
        toolbar.append(add_button)
        remove_button = create_toolbutton(self,
                                          text=_('Remove path'),
                                          icon=ima.icon('edit_remove'),
                                          triggered=self.remove_path,
                                          text_beside_icon=True)
        toolbar.append(remove_button)
        self.selection_widgets.append(remove_button)
        self._add_widgets_to_layout(layout, toolbar)
        layout.addStretch(1)
        if os.name == 'nt' and sync:
            self.sync_button = create_toolbutton(
                self,
                text=_("Synchronize..."),
                icon=ima.icon('fileimport'),
                triggered=self.synchronize,
                tip=_("Synchronize Spyder's path list with PYTHONPATH "
                      "environment variable"),
                text_beside_icon=True)
            layout.addWidget(self.sync_button)
        return toolbar

    @Slot()
    def synchronize(self):
        """
        Synchronize Spyder's path list with PYTHONPATH environment variable
        Only apply to: current user, on Windows platforms
        """
        answer = QMessageBox.question(
            self, _("Synchronize"),
            _("This will synchronize Spyder's path list with "
              "<b>PYTHONPATH</b> environment variable for current user, "
              "allowing you to run your Python modules outside Spyder "
              "without having to configure sys.path. "
              "<br>Do you want to clear contents of PYTHONPATH before "
              "adding Spyder's path list?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if answer == QMessageBox.Cancel:
            return
        elif answer == QMessageBox.Yes:
            remove = True
        else:
            remove = False
        from spyder.utils.environ import (get_user_env, set_user_env,
                                          listdict2envdict)
        env = get_user_env()
        if remove:
            ppath = self.active_pathlist + self.ro_pathlist
        else:
            ppath = env.get('PYTHONPATH', [])
            if not isinstance(ppath, list):
                ppath = [ppath]
            ppath = [
                path for path in ppath
                if path not in (self.active_pathlist + self.ro_pathlist)
            ]
            ppath.extend(self.active_pathlist + self.ro_pathlist)
        env['PYTHONPATH'] = ppath
        set_user_env(listdict2envdict(env), parent=self)

    def get_path_list(self):
        """Return path list (does not include the read-only path list)"""
        return self.pathlist

    def update_not_active_pathlist(self, item):
        path = item.text()
        if bool(item.checkState()) is True:
            self.remove_from_not_active_pathlist(path)
        else:
            self.add_to_not_active_pathlist(path)

    def add_to_not_active_pathlist(self, path):
        if path not in self.not_active_pathlist:
            self.not_active_pathlist.append(path)

    def remove_from_not_active_pathlist(self, path):
        if path in self.not_active_pathlist:
            self.not_active_pathlist.remove(path)

    def update_list(self):
        """Update path list"""
        self.listwidget.clear()
        for name in self.pathlist + self.ro_pathlist:
            item = QListWidgetItem(name)
            item.setIcon(ima.icon('DirClosedIcon'))
            if name in self.ro_pathlist:
                item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            elif name in self.not_active_pathlist:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Unchecked)
            else:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            self.listwidget.addItem(item)
        self.refresh()

    def refresh(self, row=None):
        """Refresh widget"""
        for widget in self.selection_widgets:
            widget.setEnabled(self.listwidget.currentItem() is not None)
        not_empty = self.listwidget.count() > 0
        if self.sync_button is not None:
            self.sync_button.setEnabled(not_empty)

    def move_to(self, absolute=None, relative=None):
        index = self.listwidget.currentRow()
        if absolute is not None:
            if absolute:
                new_index = len(self.pathlist) - 1
            else:
                new_index = 0
        else:
            new_index = index + relative
        new_index = max(0, min(len(self.pathlist) - 1, new_index))
        path = self.pathlist.pop(index)
        self.pathlist.insert(new_index, path)
        self.update_list()
        self.listwidget.setCurrentRow(new_index)

    @Slot()
    def remove_path(self):
        answer = QMessageBox.warning(
            self, _("Remove path"),
            _("Do you really want to remove selected path?"),
            QMessageBox.Yes | QMessageBox.No)
        if answer == QMessageBox.Yes:
            self.pathlist.pop(self.listwidget.currentRow())
            self.remove_from_not_active_pathlist(
                self.listwidget.currentItem().text())
            self.update_list()

    @Slot()
    def add_path(self):
        self.redirect_stdio.emit(False)
        directory = getexistingdirectory(self, _("Select directory"),
                                         self.last_path)
        self.redirect_stdio.emit(True)
        if directory:
            is_unicode = False
            if PY2:
                try:
                    directory.decode('ascii')
                except UnicodeEncodeError:
                    is_unicode = True
                if is_unicode:
                    QMessageBox.warning(
                        self, _("Add path"),
                        _("You are using Python 2 and the path"
                          " selected has Unicode characters. "
                          "The new path will not be added."), QMessageBox.Ok)
                    return
            directory = osp.abspath(directory)
            self.last_path = directory
            if directory in self.pathlist:
                item = self.listwidget.findItems(directory, Qt.MatchExactly)[0]
                item.setCheckState(Qt.Checked)
                answer = QMessageBox.question(
                    self, _("Add path"),
                    _("This directory is already included in Spyder "
                      "path list.<br>Do you want to move it to the "
                      "top of the list?"), QMessageBox.Yes | QMessageBox.No)
                if answer == QMessageBox.Yes:
                    self.pathlist.remove(directory)
                else:
                    return
            self.pathlist.insert(0, directory)
            self.update_list()
コード例 #5
0
class MainWindow(QMainWindow):
    FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = 0, 1, 2

    def __init__(self, label_file=None):
        super(MainWindow, self).__init__()
        self.showMaximized()
        self.setWindowTitle("VTCC.Labelling")

        self.file_dirs = []
        self.file_id = -1
        self.labels = []
        with open(label_file, 'r') as f:
            lines = f.read().splitlines()
            for label in lines:
                self.labels.append(label)

        # RIGHT DOCK
        self.label_dock = QDockWidget("Label List", self)
        self.label_list_widget = QListWidget(self)
        self.load_labels(label_file)
        self.label_dock.setWidget(self.label_list_widget)

        self.object_dock = QDockWidget("Object List", self)
        self.object_list_widget = QListWidget(self)
        self.object_list_widget.currentRowChanged.connect(self.change_object)
        self.object_dock.setWidget(self.object_list_widget)

        self.file_dock = QDockWidget("File List", self)
        self.file_list_widget = QListWidget(self)
        self.file_list_widget.currentRowChanged.connect(self.change_file)
        self.file_dock.setWidget(self.file_list_widget)

        self.addDockWidget(Qt.RightDockWidgetArea, self.label_dock)
        self.addDockWidget(Qt.RightDockWidgetArea, self.object_dock)
        self.addDockWidget(Qt.RightDockWidgetArea, self.file_dock)

        # MAIN CANVAS
        self.canvas = Canvas(self)

        self.canvas_area = QScrollArea()
        self.canvas_area.setWidget(self.canvas)
        self.canvas_area.setWidgetResizable(True)
        self.scrollBars = {
            Qt.Vertical: self.canvas_area.verticalScrollBar(),
            Qt.Horizontal: self.canvas_area.horizontalScrollBar(),
        }
        self.setCentralWidget(self.canvas_area)

        # LEFT DOCK
        self.open_action = QAction(QIcon('icons/open.png'), 'Open File', self)
        self.open_action.triggered.connect(self.open_file)
        self.open_action.setShortcut(QKeySequence("Ctrl+O"))
        self.open_dir_action = QAction(QIcon('icons/open.png'), 'Open Dir',
                                       self)
        self.open_dir_action.triggered.connect(self.open_dir)

        self.next_img_action = QAction(QIcon('icons/next.png'), 'Next Image',
                                       self)
        self.next_img_action.triggered.connect(self.next_img)
        self.next_img_action.setShortcut(QKeySequence("Right"))
        self.prev_img_action = QAction(QIcon('icons/prev.png'), 'Prev Image',
                                       self)
        self.prev_img_action.triggered.connect(self.prev_img)
        self.prev_img_action.setShortcut(QKeySequence("Left"))

        self.zoom_in_action = QAction(QIcon('icons/zoom-in.png'), 'Zoom In',
                                      self)
        self.zoom_in_action.triggered.connect(self.zoom_in)
        self.zoom_out_action = QAction(QIcon('icons/zoom-out.png'), 'Zoom Out',
                                       self)
        self.zoom_out_action.triggered.connect(self.zoom_out)
        self.zoom_org_action = QAction(QIcon('icons/fit-window.png'),
                                       'Fit Window', self)
        self.zoom_org_action.triggered.connect(self.zoom_org)

        self.rectangle_action = QAction(QIcon('icons/objects.png'),
                                        'New Rectangle', self)
        self.rectangle_action.triggered.connect(self.new_rectangle)
        self.auto_polygon_action = QAction(QIcon('icons/objects.png'),
                                           'New Auto-Polygon', self)
        self.auto_polygon_action.triggered.connect(self.new_auto_polygon)
        self.polygon_action = QAction(QIcon('icons/objects.png'),
                                      'New Polygon', self)
        self.polygon_action.triggered.connect(self.new_polygon)

        self.next_obj_action = QAction(QIcon('icons/next.png'), 'Next Object',
                                       self)
        self.next_obj_action.triggered.connect(self.canvas.next_obj)
        self.next_obj_action.setShortcut(QKeySequence("Down"))
        self.prev_obj_action = QAction(QIcon('icons/prev.png'), 'Prev Object',
                                       self)
        self.prev_obj_action.triggered.connect(self.canvas.prev_obj)
        self.prev_obj_action.setShortcut(QKeySequence("Up"))
        self.del_obj_action = QAction(QIcon('icons/delete.png'),
                                      'Delete Object', self)
        self.del_obj_action.triggered.connect(self.canvas.del_obj)
        self.del_obj_action.setShortcut(QKeySequence("Del"))

        self.toolbar = QToolBar(self)
        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        self.toolbar.addAction(self.open_action)
        self.toolbar.addAction(self.open_dir_action)
        self.toolbar.addAction(self.next_img_action)
        self.toolbar.addAction(self.prev_img_action)
        # self.toolbar.addAction(self.zoom_in_action)
        # self.toolbar.addAction(self.zoom_out_action)
        # self.toolbar.addAction(self.zoom_org_action)
        self.toolbar.addAction(self.rectangle_action)
        self.toolbar.addAction(self.auto_polygon_action)
        self.toolbar.addAction(self.polygon_action)
        self.toolbar.addAction(self.next_obj_action)
        self.toolbar.addAction(self.prev_obj_action)
        self.toolbar.addAction(self.del_obj_action)

        self.addToolBar(Qt.LeftToolBarArea, self.toolbar)

        self.scalers = {
            self.FIT_WINDOW:
            self.scaleFitWindow,
            self.FIT_WIDTH:
            self.scaleFitWidth,
            # Set to one to scale to 100% when loading files.
            self.MANUAL_ZOOM:
            lambda: 1,
        }

    def update_mode(self, mode_id):
        pass

    def change_object(self, row):
        if (row >= 0):
            self.canvas.cur_object = row
            self.canvas.repaint()

    def change_file(self, row):
        if (row >= 0):
            self.file_id = row
            self.canvas.load_file(self.file_dirs[self.file_id])
            self.adjustScale(initial=True)

    def open_file(self):
        path = '.'
        if len(self.file_dirs) > 0:
            path = os.path.dirname(str(self.file_dirs[0]))

        formats = [
            '*.{}'.format(fmt.data().decode())
            for fmt in QImageReader.supportedImageFormats()
        ]
        filters = "Image files (%s)" % ' '.join(formats)
        file_dir = QFileDialog.getOpenFileName(self, \
                    "Choose Image file", path, filters)[0]

        self.file_dirs = [file_dir]
        self.import_files()

    def open_dir(self):
        targetDirPath = str(
            QFileDialog.getExistingDirectory(
                self, 'Open Directory', '.',
                QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks))

        self.file_dirs = []
        for fmt in QImageReader.supportedImageFormats():
            pattern = os.path.join(targetDirPath, "*." + fmt.data().decode())
            self.file_dirs += glob.glob(pattern)
        self.import_files()

    def next_img(self):
        if (len(self.file_dirs) > 0):
            self.file_id = (self.file_id + 1) % len(self.file_dirs)
            self.canvas.load_file(self.file_dirs[self.file_id])
            self.adjustScale(initial=True)
            self.file_list_widget.setCurrentRow(self.file_id)

    def prev_img(self):
        if (len(self.file_dirs) > 0):
            self.file_id = (self.file_id + len(self.file_dirs) - 1) % len(
                self.file_dirs)
            self.canvas.load_file(self.file_dirs[self.file_id])
            self.adjustScale(initial=True)
            self.file_list_widget.setCurrentRow(self.file_id)

    def import_files(self):
        self.load_file_list()

        self.file_id = 0
        self.canvas.load_file(self.file_dirs[0])
        self.adjustScale(initial=True)
        self.file_list_widget.setCurrentRow(self.file_id)

    def load_labels(self, label_file):
        self.label_list_widget.clear()
        for label in self.labels:
            item = QListWidgetItem(label)
            self.label_list_widget.addItem(item)

    def load_object_list(self, objects):
        self.object_list_widget.clear()
        for obj in objects:
            item = QListWidgetItem(obj.label)
            self.object_list_widget.addItem(item)

    def load_file_list(self):
        self.file_list_widget.clear()

        for image_dir in self.file_dirs:
            if not QFile.exists(image_dir):
                continue
            item = QListWidgetItem(image_dir)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            label_dir = os.path.splitext(image_dir)[0] + '.json'
            if QFile.exists(label_dir):
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)
            self.file_list_widget.addItem(item)

    def scaleFitWindow(self):
        """Figure out the size of the pixmap to fit the main widget."""
        e = 2.0  # So that no scrollbars are generated.
        w1 = self.centralWidget().width() - e
        h1 = self.centralWidget().height() - e
        a1 = w1 / h1
        # Calculate a new scale value based on the pixmap's aspect ratio.
        w2 = self.canvas.pixmap.width() - 0.0
        h2 = self.canvas.pixmap.height() - 0.0
        a2 = w2 / h2
        return w1 / w2 if a2 >= a1 else h1 / h2

    def scaleFitWidth(self):
        # The epsilon does not seem to work too well here.
        w = self.centralWidget().width() - 2.0
        return w / self.canvas.pixmap.width()

    def adjustScale(self, initial=False):
        value = self.scalers[self.FIT_WINDOW if initial else self.zoomMode]()
        self.canvas.rescale(value)

    def zoom_in(self):
        value = self.canvas.scale
        self.canvas.rescale(value * 1.1)

    def zoom_out(self):
        value = self.canvas.scale
        self.canvas.rescale(value * 0.9)

    def zoom_org(self):
        print(self.centralWidget().width(), self.centralWidget().height())
        print(self.canvas.pixmap.width(), self.canvas.pixmap.height())
        print(self.canvas.width(), self.canvas.height())
        print(self.canvas_area.width(), self.canvas_area.height())
        self.adjustScale(initial=True)

    def new_rectangle(self):
        self.canvas.points = []
        self.canvas.mode = self.canvas.RECTANGLE

    def new_auto_polygon(self):
        self.canvas.points = []
        self.canvas.mode = self.canvas.AUTO_POLYGON

    def new_polygon(self):
        self.canvas.points = []
        self.canvas.mode = self.canvas.POLYGON
コード例 #6
0
ファイル: fileassociations.py プロジェクト: nixjdm/spyder
class FileAssociationsWidget(QWidget):
    """Widget to add applications association to file extensions."""

    # This allows validating a single extension entry or a list of comma
    # separated values (eg `*.json` or `*.json,*.txt,MANIFEST.in`)
    _EXTENSIONS_LIST_REGEX = (r'(?:(?:\*{1,1}|\w+)\.\w+)'
                              r'(?:,(?:\*{1,1}|\w+)\.\w+){0,20}')
    sig_data_changed = Signal(dict)

    def __init__(self, parent=None):
        """Widget to add applications association to file extensions."""
        super(FileAssociationsWidget, self).__init__(parent=parent)

        # Variables
        self._data = {}
        self._dlg_applications = None
        self._dlg_input = None
        self._regex = re.compile(self._EXTENSIONS_LIST_REGEX)

        # Widgets
        self.label = QLabel(
            _('Here you can associate which external applications you want'
              'to use to open specific file extensions <br> (e.g. .txt '
              'files with Notepad++ or .csv files with Excel).'))
        self.label_extensions = QLabel(_('File types:'))
        self.list_extensions = QListWidget()
        self.button_add = QPushButton(_('Add'))
        self.button_remove = QPushButton(_('Remove'))
        self.button_edit = QPushButton(_('Edit'))

        self.label_applications = QLabel(_('Associated applications:'))
        self.list_applications = QListWidget()
        self.button_add_application = QPushButton(_('Add'))
        self.button_remove_application = QPushButton(_('Remove'))
        self.button_default = QPushButton(_('Set default'))

        # Layout
        layout_extensions = QHBoxLayout()
        layout_extensions.addWidget(self.list_extensions, 4)

        layout_buttons_extensions = QVBoxLayout()
        layout_buttons_extensions.addWidget(self.button_add)
        layout_buttons_extensions.addWidget(self.button_remove)
        layout_buttons_extensions.addWidget(self.button_edit)
        layout_buttons_extensions.addStretch()

        layout_applications = QHBoxLayout()
        layout_applications.addWidget(self.list_applications, 4)

        layout_buttons_applications = QVBoxLayout()
        layout_buttons_applications.addWidget(self.button_add_application)
        layout_buttons_applications.addWidget(self.button_remove_application)
        layout_buttons_applications.addWidget(self.button_default)
        layout_buttons_applications.addStretch()

        layout_extensions.addLayout(layout_buttons_extensions, 2)
        layout_applications.addLayout(layout_buttons_applications, 2)

        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.label_extensions)
        layout.addLayout(layout_extensions)
        layout.addWidget(self.label_applications)
        layout.addLayout(layout_applications)

        self.setLayout(layout)

        # Signals
        self.button_add.clicked.connect(lambda: self.add_association())
        self.button_remove.clicked.connect(self.remove_association)
        self.button_edit.clicked.connect(self.edit_association)
        self.button_add_application.clicked.connect(self.add_application)
        self.button_remove_application.clicked.connect(self.remove_application)
        self.button_default.clicked.connect(self.set_default_application)
        self.list_extensions.currentRowChanged.connect(self.update_extensions)
        self.list_extensions.itemDoubleClicked.connect(self.edit_association)
        self.list_applications.currentRowChanged.connect(
            self.update_applications)
        self._refresh()
        self._create_association_dialog()

    def _refresh(self):
        """Refresh the status of buttons on widget."""
        self.setUpdatesEnabled(False)
        for widget in [
                self.button_remove, self.button_add_application,
                self.button_edit, self.button_remove_application,
                self.button_default
        ]:
            widget.setDisabled(True)

        item = self.list_extensions.currentItem()
        if item:
            for widget in [
                    self.button_remove, self.button_add_application,
                    self.button_remove_application, self.button_edit
            ]:
                widget.setDisabled(False)
        self.update_applications()
        self.setUpdatesEnabled(True)

    def _add_association(self, value):
        """Add association helper."""
        # Check value is not pressent
        for row in range(self.list_extensions.count()):
            item = self.list_extensions.item(row)
            if item.text().strip() == value.strip():
                break
        else:
            item = QListWidgetItem(value)
            self.list_extensions.addItem(item)
            self.list_extensions.setCurrentItem(item)

        self._refresh()

    def _add_application(self, app_name, fpath):
        """Add application helper."""
        app_not_found_text = _(' (Application not found!)')
        for row in range(self.list_applications.count()):
            item = self.list_applications.item(row)
            # Ensure the actual name is checked without the `app not found`
            # additional text, in case app was not found
            item_text = item.text().replace(app_not_found_text, '').strip()
            if item and item_text == app_name:
                break
        else:
            icon = get_application_icon(fpath)

            if not (os.path.isfile(fpath) or os.path.isdir(fpath)):
                app_name += app_not_found_text

            item = QListWidgetItem(icon, app_name)
            self.list_applications.addItem(item)
            self.list_applications.setCurrentItem(item)

        if not (os.path.isfile(fpath) or os.path.isdir(fpath)):
            item.setToolTip(_('Application not found!'))

    def _update_extensions(self):
        """Update extensions list."""
        self.list_extensions.clear()
        for extension, _ in sorted(self._data.items()):
            self._add_association(extension)

        # Select first item
        self.list_extensions.setCurrentRow(0)
        self.update_extensions()
        self.update_applications()

    def _create_association_dialog(self):
        """Create input extension dialog and save it to for reuse."""
        self._dlg_input = InputTextDialog(
            self,
            title=_('File association'),
            label=(_('Enter new file extension. You can add several values '
                     'separated by commas.<br>Examples include:') +
                   '<ul><li><code>*.txt</code></li>' +
                   '<li><code>*.json,*,csv</code></li>' +
                   '<li><code>*.json,README.md</code></li></ul>'),
        )
        self._dlg_input.set_regex_validation(self._EXTENSIONS_LIST_REGEX)

    def load_values(self, data=None):
        """
        Load file associations data.

        Format {'*.ext': [['Application Name', '/path/to/app/executable']]}

        `/path/to/app/executable` is an executable app on mac and windows and
        a .desktop xdg file on linux.
        """
        self._data = data
        self._update_extensions()

    def add_association(self, value=None):
        """Add extension file association."""
        if value is None:
            text, ok_pressed = '', False
            self._dlg_input.set_text('')

            if self._dlg_input.exec_():
                text = self._dlg_input.text()
                ok_pressed = True
        else:
            match = self._regex.match(value)
            text, ok_pressed = value, bool(match)

        if ok_pressed:
            if text not in self._data:
                self._data[text] = []
                self._add_association(text)
                self.check_data_changed()

    def remove_association(self):
        """Remove extension file association."""
        if self._data:
            if self.current_extension:
                self._data.pop(self.current_extension)
                self._update_extensions()
                self._refresh()
                self.check_data_changed()

    def edit_association(self):
        """Edit text of current selected association."""
        old_text = self.current_extension
        self._dlg_input.set_text(old_text)

        if self._dlg_input.exec_():
            new_text = self._dlg_input.text()
            if old_text != new_text:
                values = self._data.pop(self.current_extension)
                self._data[new_text] = values
                self._update_extensions()
                self._refresh()
                for row in range(self.list_extensions.count()):
                    item = self.list_extensions.item(row)
                    if item.text() == new_text:
                        self.list_extensions.setCurrentItem(item)
                        break
                self.check_data_changed()

    def add_application(self):
        """Remove application to selected extension."""
        if self.current_extension:
            if self._dlg_applications is None:
                self._dlg_applications = ApplicationsDialog(self)

            self._dlg_applications.set_extension(self.current_extension)

            if self._dlg_applications.exec_():
                app_name = self._dlg_applications.application_name
                fpath = self._dlg_applications.application_path
                self._data[self.current_extension].append((app_name, fpath))
                self._add_application(app_name, fpath)
                self.check_data_changed()

    def remove_application(self):
        """Remove application from selected extension."""
        current_row = self.list_applications.currentRow()
        values = self._data.get(self.current_extension)
        if values and current_row != -1:
            values.pop(current_row)
            self.update_extensions()
            self.update_applications()
            self.check_data_changed()

    def set_default_application(self):
        """
        Set the selected item on the application list as default application.
        """
        current_row = self.list_applications.currentRow()
        if current_row != -1:
            values = self._data[self.current_extension]
            value = values.pop(current_row)
            values.insert(0, value)
            self._data[self.current_extension] = values
            self.update_extensions()
            self.check_data_changed()

    def update_extensions(self, row=None):
        """Update extensiosn list after additions or deletions."""
        self.list_applications.clear()
        for extension, values in self._data.items():
            if extension.strip() == self.current_extension:
                for (app_name, fpath) in values:
                    self._add_application(app_name, fpath)
                break
        self.list_applications.setCurrentRow(0)
        self._refresh()

    def update_applications(self, row=None):
        """Update application list after additions or deletions."""
        current_row = self.list_applications.currentRow()
        self.button_default.setEnabled(current_row != 0)

    def check_data_changed(self):
        """Check if data has changed and emit signal as needed."""
        self.sig_data_changed.emit(self._data)

    @property
    def current_extension(self):
        """Return the current selected extension text."""
        item = self.list_extensions.currentItem()
        if item:
            return item.text()

    @property
    def data(self):
        """Return the current file associations data."""
        return self._data.copy()
コード例 #7
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    valueChanged = Signal()

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def _restart_dialog(self, event=None, extra_str=""):
        """Displays the dialog informing user a restart is required.

        Paramters
        ---------
        event : Event
        extra_str : str
            Extra information to add to the message about needing a restart.
        """

        text_str = trans._(
            "napari requires a restart for image rendering changes to apply.")

        widget = ResetNapariInfoDialog(
            parent=self,
            text=text_str,
        )
        widget.exec_()

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        settings = get_settings()
        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.
        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in settings.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """
        schema = json.loads(setting['json_schema'])

        # Resolve allOf references
        definitions = schema.get("definitions", {})
        if definitions:
            for key, data in schema["properties"].items():
                if "allOf" in data:
                    allof = data["allOf"]
                    allof = [d["$ref"].rsplit("/")[-1] for d in allof]
                    for definition in allof:
                        local_def = definitions[definition]
                        schema["properties"][key]["enum"] = local_def["enum"]
                        schema["properties"][key]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""
        self._reset_dialog = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        self._reset_dialog.valueChanged.connect(self._reset_widgets)
        self._reset_dialog.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self.valueChanged.emit()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to settings."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        settings = get_settings()
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = settings.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        settings = get_settings()
        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)

        # Disable widgets that loaded settings from environment variables
        section = schema["section"]
        form_layout = form.widget.layout()
        for row in range(form.widget.layout().rowCount()):
            widget = form_layout.itemAt(row, form_layout.FieldRole).widget()
            name = widget._name
            disable = bool(
                settings._env_settings.get(section, {}).get(name, None))
            widget.setDisabled(disable)
            try:
                widget.opacity.setOpacity(0.3 if disable else 1)
            except AttributeError:
                # some widgets may not have opacity (such as the QtPluginSorter)
                pass

        # set state values for widget
        form.widget.state = values

        if section == 'experimental':
            # need to disable async if octree is enabled.
            if values['octree'] is True:
                form = self._disable_async(form, values)

        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _disable_async(self, form, values, disable=True, state=True):
        """Disable async if octree is True."""
        settings = get_settings()
        # need to make sure that if async_ is an environment setting, that we don't
        # enable it here.
        if (settings._env_settings['experimental'].get('async_', None)
                is not None):
            disable = True

        idx = list(values.keys()).index('async_')
        form_layout = form.widget.layout()
        widget = form_layout.itemAt(idx, form_layout.FieldRole).widget()
        widget.opacity.setOpacity(0.3 if disable else 1)
        widget.setDisabled(disable)

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def set_current_index(self, index: int):
        """
        Set the current page on the preferences by index.

        Parameters
        ----------
        index : int
            Index of page to set as current one.
        """
        self._list.setCurrentRow(index)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        settings = get_settings()
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in settings
            for setting_name, value in different_values.items():
                try:
                    setattr(settings._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict

                    if page == 'experimental':

                        if setting_name == 'octree':

                            # disable/enable async checkbox
                            widget = self._stack.currentWidget()
                            cstate = True if value is True else False
                            self._disable_async(widget,
                                                new_dict,
                                                disable=cstate)

                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                        elif setting_name == 'async_':
                            # need to inform user that napari restart needed.
                            self._restart_dialog()

                except:  # noqa: E722
                    continue
コード例 #8
0
class MeasurementSettings(QWidget):
    """
    :type settings: Settings
    """
    def __init__(self, settings: PartSettings, parent=None):
        super().__init__(parent)
        self.chosen_element: Optional[MeasurementListWidgetItem] = None
        self.chosen_element_area: Optional[Tuple[AreaType, float]] = None
        self.settings = settings
        self.profile_list = QListWidget(self)
        self.profile_description = QTextEdit(self)
        self.profile_description.setReadOnly(True)
        self.profile_options = QListWidget()
        self.profile_options_chosen = QListWidget()
        self.measurement_area_choose = QEnumComboBox(enum_class=AreaType)
        self.per_component = QEnumComboBox(enum_class=PerComponent)
        self.power_num = QDoubleSpinBox()
        self.power_num.setDecimals(3)
        self.power_num.setRange(-100, 100)
        self.power_num.setValue(1)
        self.choose_butt = QPushButton("→", self)
        self.discard_butt = QPushButton("←", self)
        self.proportion_butt = QPushButton("Ratio", self)
        self.proportion_butt.setToolTip("Create proportion from two parameter")
        self.move_up = QPushButton("↑", self)
        self.move_down = QPushButton("↓", self)
        self.remove_button = QPushButton("Remove")
        self.save_butt = QPushButton("Save")
        self.save_butt.setToolTip(
            "Set name for set and choose at least one parameter")
        self.save_butt_with_name = QPushButton(
            "Save with custom parameters designation")
        self.save_butt_with_name.setToolTip(
            "Set name for set and choose at least one parameter")
        self.reset_butt = QPushButton("Clear")
        self.soft_reset_butt = QPushButton("Remove user parameters")
        self.profile_name = QLineEdit(self)

        self.delete_profile_butt = QPushButton("Delete ")
        self.export_profiles_butt = QPushButton("Export")
        self.import_profiles_butt = QPushButton("Import")
        self.edit_profile_butt = QPushButton("Edit")

        self.choose_butt.setDisabled(True)
        self.choose_butt.clicked.connect(self.choose_option)
        self.discard_butt.setDisabled(True)
        self.discard_butt.clicked.connect(self.discard_option)
        self.proportion_butt.setDisabled(True)
        self.proportion_butt.clicked.connect(self.proportion_action)
        self.save_butt.setDisabled(True)
        self.save_butt.clicked.connect(self.save_action)
        self.save_butt_with_name.setDisabled(True)
        self.save_butt_with_name.clicked.connect(self.named_save_action)
        self.profile_name.textChanged.connect(self.name_changed)
        self.move_down.setDisabled(True)
        self.move_down.clicked.connect(self.move_down_fun)
        self.move_up.setDisabled(True)
        self.move_up.clicked.connect(self.move_up_fun)
        self.remove_button.setDisabled(True)
        self.remove_button.clicked.connect(self.remove_element)
        self.reset_butt.clicked.connect(self.reset_action)
        self.soft_reset_butt.clicked.connect(self.soft_reset)
        self.delete_profile_butt.setDisabled(True)
        self.delete_profile_butt.clicked.connect(self.delete_profile)
        self.export_profiles_butt.clicked.connect(
            self.export_measurement_profiles)
        self.import_profiles_butt.clicked.connect(
            self.import_measurement_profiles)
        self.edit_profile_butt.clicked.connect(self.edit_profile)

        self.profile_list.itemSelectionChanged.connect(self.profile_chosen)
        self.profile_options.itemSelectionChanged.connect(
            self.create_selection_changed)
        self.profile_options_chosen.itemSelectionChanged.connect(
            self.create_selection_chosen_changed)
        self.settings.measurement_profiles_changed.connect(
            self._refresh_profiles)

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Measurement set:"))
        profile_layout = QHBoxLayout()
        profile_layout.addWidget(self.profile_list)
        profile_layout.addWidget(self.profile_description)
        profile_buttons_layout = QHBoxLayout()
        profile_buttons_layout.addWidget(self.delete_profile_butt)
        profile_buttons_layout.addWidget(self.export_profiles_butt)
        profile_buttons_layout.addWidget(self.import_profiles_butt)
        profile_buttons_layout.addWidget(self.edit_profile_butt)
        profile_buttons_layout.addStretch()
        layout.addLayout(profile_layout)
        layout.addLayout(profile_buttons_layout)
        heading_layout = QHBoxLayout()
        # heading_layout.addWidget(QLabel("Create profile"), 1)
        heading_layout.addWidget(h_line(), 6)
        layout.addLayout(heading_layout)
        name_layout = QHBoxLayout()
        name_layout.addWidget(QLabel("Set name:"))
        name_layout.addWidget(self.profile_name)
        name_layout.addStretch()
        name_layout.addWidget(QLabel("Per component:"))
        name_layout.addWidget(self.per_component)
        name_layout.addWidget(QLabel("Area:"))
        name_layout.addWidget(self.measurement_area_choose)
        name_layout.addWidget(QLabel("to power:"))
        name_layout.addWidget(self.power_num)
        layout.addLayout(name_layout)
        create_layout = QHBoxLayout()
        create_layout.addWidget(self.profile_options)
        butt_op_layout = QVBoxLayout()
        butt_op_layout.addStretch()
        butt_op_layout.addWidget(self.choose_butt)
        butt_op_layout.addWidget(self.discard_butt)
        butt_op_layout.addWidget(self.proportion_butt)
        butt_op_layout.addWidget(self.reset_butt)
        butt_op_layout.addStretch()
        create_layout.addLayout(butt_op_layout)
        create_layout.addWidget(self.profile_options_chosen)
        butt_move_layout = QVBoxLayout()
        butt_move_layout.addStretch()
        butt_move_layout.addWidget(self.move_up)
        butt_move_layout.addWidget(self.move_down)
        butt_move_layout.addWidget(self.remove_button)
        butt_move_layout.addStretch()
        create_layout.addLayout(butt_move_layout)
        layout.addLayout(create_layout)
        save_butt_layout = QHBoxLayout()
        save_butt_layout.addWidget(self.soft_reset_butt)
        save_butt_layout.addStretch()
        save_butt_layout.addWidget(self.save_butt)
        save_butt_layout.addWidget(self.save_butt_with_name)
        layout.addLayout(save_butt_layout)
        self.setLayout(layout)

        for profile in MEASUREMENT_DICT.values():
            help_text = profile.get_description()
            lw = MeasurementListWidgetItem(profile.get_starting_leaf())
            lw.setToolTip(help_text)
            self.profile_options.addItem(lw)
        self._refresh_profiles()

    def _refresh_profiles(self):
        item = self.profile_list.currentItem()
        items = list(self.settings.measurement_profiles.keys())
        try:
            index = items.index(item.text())
        except (ValueError, AttributeError):
            index = -1

        self.profile_list.clear()
        self.profile_list.addItems(items)
        self.profile_list.setCurrentRow(index)

        if self.profile_list.count() == 0:
            self.export_profiles_butt.setDisabled(True)
        else:
            self.export_profiles_butt.setEnabled(True)

    def remove_element(self):
        elem = self.profile_options_chosen.currentItem()
        if elem is None:
            return
        index = self.profile_options_chosen.currentRow()
        self.profile_options_chosen.takeItem(index)
        if self.profile_options_chosen.count() == 0:
            self.move_down.setDisabled(True)
            self.move_up.setDisabled(True)
            self.remove_button.setDisabled(True)
            self.discard_butt.setDisabled(True)
            self.save_butt.setDisabled(True)
            self.save_butt_with_name.setDisabled(True)

    def delete_profile(self):
        item = self.profile_list.currentItem()
        del self.settings.measurement_profiles[str(item.text())]

    def profile_chosen(self):
        self.delete_profile_butt.setEnabled(True)
        if self.profile_list.count() == 0:
            self.profile_description.setText("")
            return
        item = self.profile_list.currentItem()
        if item is None:
            self.profile_description.setText("")
            return
        profile = self.settings.measurement_profiles[item.text()]
        self.profile_description.setText(str(profile))

    def create_selection_changed(self):
        self.choose_butt.setEnabled(True)
        self.proportion_butt.setEnabled(True)

    def proportion_action(self):
        # TODO use get_parameters
        if self.chosen_element is None:
            item = self.profile_options.currentItem()
            self.chosen_element_area = self.get_parameters(
                deepcopy(item.stat),
                self.measurement_area_choose.currentEnum(),
                self.per_component.currentEnum(),
                self.power_num.value(),
            )
            if self.chosen_element_area is None:
                return
            self.chosen_element = item
            item.setIcon(QIcon(os.path.join(icons_dir, "task-accepted.png")))
        elif (self.profile_options.currentItem() == self.chosen_element
              and self.measurement_area_choose.currentEnum()
              == self.chosen_element_area.area
              and self.per_component.currentEnum()
              == self.chosen_element_area.per_component):
            self.chosen_element.setIcon(QIcon())
            self.chosen_element = None
        else:
            item: MeasurementListWidgetItem = self.profile_options.currentItem(
            )
            leaf = self.get_parameters(
                deepcopy(item.stat),
                self.measurement_area_choose.currentEnum(),
                self.per_component.currentEnum(),
                self.power_num.value(),
            )
            if leaf is None:
                return
            lw = MeasurementListWidgetItem(
                Node(op="/", left=self.chosen_element_area, right=leaf))
            lw.setToolTip("User defined")
            self._add_option(lw)
            self.chosen_element.setIcon(QIcon())
            self.chosen_element = None
            self.chosen_element_area = None

    def _add_option(self, item: MeasurementListWidgetItem):
        for i in range(self.profile_options_chosen.count()):
            if item.text() == self.profile_options_chosen.item(i).text():
                return
        self.profile_options_chosen.addItem(item)
        if self.good_name():
            self.save_butt.setEnabled(True)
            self.save_butt_with_name.setEnabled(True)
        if self.profile_options.count() == 0:
            self.choose_butt.setDisabled(True)

    def create_selection_chosen_changed(self):
        # print(self.profile_options_chosen.count())
        self.remove_button.setEnabled(True)
        if self.profile_options_chosen.count() == 0:
            self.move_down.setDisabled(True)
            self.move_up.setDisabled(True)
            self.remove_button.setDisabled(True)
            return
        self.discard_butt.setEnabled(True)
        if self.profile_options_chosen.currentRow() != 0:
            self.move_up.setEnabled(True)
        else:
            self.move_up.setDisabled(True)
        if self.profile_options_chosen.currentRow(
        ) != self.profile_options_chosen.count() - 1:
            self.move_down.setEnabled(True)
        else:
            self.move_down.setDisabled(True)

    def good_name(self):
        return str(self.profile_name.text()).strip() != ""

    def move_down_fun(self):
        row = self.profile_options_chosen.currentRow()
        item = self.profile_options_chosen.takeItem(row)
        self.profile_options_chosen.insertItem(row + 1, item)
        self.profile_options_chosen.setCurrentRow(row + 1)
        self.create_selection_chosen_changed()

    def move_up_fun(self):
        row = self.profile_options_chosen.currentRow()
        item = self.profile_options_chosen.takeItem(row)
        self.profile_options_chosen.insertItem(row - 1, item)
        self.profile_options_chosen.setCurrentRow(row - 1)
        self.create_selection_chosen_changed()

    def name_changed(self):
        if self.good_name() and self.profile_options_chosen.count() > 0:
            self.save_butt.setEnabled(True)
            self.save_butt_with_name.setEnabled(True)
        else:
            self.save_butt.setDisabled(True)
            self.save_butt_with_name.setDisabled(True)

    def form_dialog(self, arguments):
        return FormDialog(arguments, settings=self.settings, parent=self)

    def get_parameters(self, node: Union[Node, Leaf], area: AreaType,
                       component: PerComponent, power: float):
        if isinstance(node, Node):
            return node
        node = node.replace_(power=power)
        if node.area is None:
            node = node.replace_(area=area)
        if node.per_component is None:
            node = node.replace_(per_component=component)
        with suppress(KeyError):
            arguments = MEASUREMENT_DICT[str(node.name)].get_fields()
            if len(arguments) > 0 and len(node.dict) == 0:
                dial = self.form_dialog(arguments)
                if dial.exec_():
                    node = node._replace(dict=dial.get_values())
                else:
                    return
        return node

    def choose_option(self):
        selected_item = self.profile_options.currentItem()
        # selected_row = self.profile_options.currentRow()
        if not isinstance(selected_item, MeasurementListWidgetItem):
            raise ValueError(
                f"Current item (type: {type(selected_item)} is not instance of MeasurementListWidgetItem"
            )
        node = deepcopy(selected_item.stat)
        # noinspection PyTypeChecker
        node = self.get_parameters(node,
                                   self.measurement_area_choose.currentEnum(),
                                   self.per_component.currentEnum(),
                                   self.power_num.value())
        if node is None:
            return
        lw = MeasurementListWidgetItem(node)
        lw.setToolTip(selected_item.toolTip())
        self._add_option(lw)

    def discard_option(self):
        selected_item: MeasurementListWidgetItem = self.profile_options_chosen.currentItem(
        )
        #  selected_row = self.profile_options_chosen.currentRow()
        lw = MeasurementListWidgetItem(deepcopy(selected_item.stat))
        lw.setToolTip(selected_item.toolTip())
        self.create_selection_chosen_changed()
        for i in range(self.profile_options.count()):
            if lw.text() == self.profile_options.item(i).text():
                return
        self.profile_options.addItem(lw)

    def edit_profile(self):
        item = self.profile_list.currentItem()
        if item is None:
            return
        profile = self.settings.measurement_profiles[str(item.text())]
        self.profile_options_chosen.clear()
        self.profile_name.setText(item.text())
        for ch in profile.chosen_fields:
            self.profile_options_chosen.addItem(
                MeasurementListWidgetItem(ch.calculation_tree))
        # self.gauss_img.setChecked(profile.use_gauss_image)
        self.save_butt.setEnabled(True)
        self.save_butt_with_name.setEnabled(True)

    def save_action(self):
        if self.profile_name.text() in self.settings.measurement_profiles:
            ret = QMessageBox.warning(
                self,
                "Profile exist",
                "Profile exist\nWould you like to overwrite it?",
                QMessageBox.No | QMessageBox.Yes,
            )
            if ret == QMessageBox.No:
                return
        selected_values = []
        for i in range(self.profile_options_chosen.count()):
            element: MeasurementListWidgetItem = self.profile_options_chosen.item(
                i)
            selected_values.append(
                MeasurementEntry(element.text(), element.stat))
        stat_prof = MeasurementProfile(self.profile_name.text(),
                                       selected_values)
        self.settings.measurement_profiles[stat_prof.name] = stat_prof
        self.settings.dump()
        self.export_profiles_butt.setEnabled(True)

    def named_save_action(self):
        if self.profile_name.text() in self.settings.measurement_profiles:
            ret = QMessageBox.warning(
                self,
                "Profile exist",
                "Profile exist\nWould you like to overwrite it?",
                QMessageBox.No | QMessageBox.Yes,
            )
            if ret == QMessageBox.No:
                return
        selected_values = []
        for i in range(self.profile_options_chosen.count()):
            txt = str(self.profile_options_chosen.item(i).text())
            selected_values.append((txt, str, txt))
        val_dialog = MultipleInput("Set fields name",
                                   list(selected_values),
                                   parent=self)
        if val_dialog.exec_():
            selected_values = []
            for i in range(self.profile_options_chosen.count()):
                element: MeasurementListWidgetItem = self.profile_options_chosen.item(
                    i)
                selected_values.append(
                    MeasurementEntry(val_dialog.result[element.text()],
                                     element.stat))
            stat_prof = MeasurementProfile(self.profile_name.text(),
                                           selected_values)
            self.settings.measurement_profiles[stat_prof.name] = stat_prof
            self.export_profiles_butt.setEnabled(True)

    def reset_action(self):
        self.profile_options.clear()
        self.profile_options_chosen.clear()
        self.profile_name.setText("")
        self.save_butt.setDisabled(True)
        self.save_butt_with_name.setDisabled(True)
        self.move_down.setDisabled(True)
        self.move_up.setDisabled(True)
        self.proportion_butt.setDisabled(True)
        self.choose_butt.setDisabled(True)
        self.discard_butt.setDisabled(True)
        for profile in MEASUREMENT_DICT.values():
            help_text = profile.get_description()
            lw = MeasurementListWidgetItem(profile.get_starting_leaf())
            lw.setToolTip(help_text)
            self.profile_options.addItem(lw)

    def soft_reset(self):
        # TODO rim should not be removed
        shift = 0
        for i in range(self.profile_options.count()):
            item = self.profile_options.item(i - shift)
            if str(item.text()) not in MEASUREMENT_DICT:
                self.profile_options.takeItem(i - shift)
                if item == self.chosen_element:
                    self.chosen_element = None
                del item
                shift += 1
        self.create_selection_changed()

    def export_measurement_profiles(self):
        exp = ExportDialog(self.settings.measurement_profiles,
                           StringViewer,
                           parent=self)
        if not exp.exec_():
            return
        dial = PSaveDialog(
            "Measurement profile (*.json)",
            settings=self.settings,
            path="io.export_directory",
            caption="Export settings profiles",
        )
        dial.selectFile("measurements_profile.json")

        if dial.exec_():
            file_path = str(dial.selectedFiles()[0])
            data = {
                x: self.settings.measurement_profiles[x]
                for x in exp.get_export_list()
            }
            with open(file_path, "w", encoding="utf-8") as ff:
                json.dump(data,
                          ff,
                          cls=self.settings.json_encoder_class,
                          indent=2)

    def import_measurement_profiles(self):
        dial = PLoadDialog(
            "Measurement profile (*.json)",
            settings=self.settings,
            path="io.export_directory",
            caption="Import settings profiles",
            parent=self,
        )
        if dial.exec_():
            file_path = str(dial.selectedFiles()[0])
            stat, err = self.settings.load_part(file_path)
            if err:
                QMessageBox.warning(
                    self, "Import error",
                    "error during importing, part of data were filtered.")
            measurement_dict = self.settings.measurement_profiles
            imp = ImportDialog(stat, measurement_dict, StringViewer)
            if not imp.exec_():
                return
            for original_name, final_name in imp.get_import_list():
                measurement_dict[final_name] = stat[original_name]
            self.settings.dump()
コード例 #9
0
ファイル: groups_modify.py プロジェクト: zbhfut/pyNastran
class GroupsModify(PyDialog):
    """
    +--------------------------+
    |     Groups : Modify      |
    +--------------------------+
    |                          |
    |  Name        xxx Default |
    |  Coords      xxx Default |
    |  Elements    xxx Default |
    |  Color       xxx Default |
    |  Add         xxx Add     |
    |  Remove      xxx Remove  |
    |                          |
    |      Set  OK Cancel      |
    +--------------------------+
    """
    def __init__(self, data, win_parent=None, group_active='main'):
        PyDialog.__init__(self, data, win_parent)
        self.set_font_size(data['font_size'])
        self._updated_groups = False

        #self.out_data = data

        #print(data)
        keys = []
        self.keys = [
            group.name for key, group in sorted(iteritems(data))
            if isinstance(key, int)
        ]
        self.active_key = self.keys.index(group_active)

        group_obj = data[self.active_key]
        name = group_obj.name

        self.imain = 0
        self.nrows = len(self.keys)

        self._default_name = group_obj.name
        self._default_elements = group_obj.element_str
        self.elements_pound = group_obj.elements_pound

        self.table = QListWidget(parent=None)
        self.table.clear()
        self.table.addItems(self.keys)

        self.setWindowTitle('Groups: Modify')
        self.create_widgets()
        self.create_layout()
        self.set_connections()

        self.on_set_as_main()

    def create_widgets(self):
        """creates the menu objects"""
        # Name
        self.name = QLabel("Name:")
        self.name_set = QPushButton("Set")
        self.name_edit = QLineEdit(str(self._default_name).strip())
        self.name_button = QPushButton("Default")

        # elements
        self.elements = QLabel("Element IDs:")
        self.elements_edit = QLineEdit(str(self._default_elements).strip())
        self.elements_button = QPushButton("Default")

        # add
        self.add = QLabel("Add Elements:")
        self.add_edit = QElementEdit(self, str(''))
        self.add_button = QPushButton("Add")

        # remove
        self.remove = QLabel("Remove Elements:")
        self.remove_edit = QElementEdit(self, str(''))
        self.remove_button = QPushButton("Remove")

        # applies a unique implicitly
        self.eids = parse_patran_syntax(str(self._default_elements),
                                        pound=self.elements_pound)

        # closing
        #self.apply_button = QPushButton("Apply")
        self.ok_button = QPushButton("Close")
        #self.cancel_button = QPushButton("Cancel")

        self.set_as_main_button = QPushButton("Set As Main")
        self.create_group_button = QPushButton('Create New Group')
        self.delete_group_button = QPushButton('Delete Group')

        self.name.setEnabled(False)
        self.name_set.setEnabled(False)
        self.name_edit.setEnabled(False)
        self.name_button.setEnabled(False)
        self.elements.setEnabled(False)
        self.elements_button.setEnabled(False)
        self.elements_edit.setEnabled(False)
        self.add.setEnabled(False)
        self.add_button.setEnabled(False)
        self.add_edit.setEnabled(False)
        self.remove.setEnabled(False)
        self.remove_button.setEnabled(False)
        self.remove_edit.setEnabled(False)
        self.delete_group_button.setEnabled(False)
        #self.apply_button.setEnabled(False)
        #self.ok_button.setEnabled(False)

    def create_layout(self):
        """displays the menu objects"""
        grid = QGridLayout()
        grid.addWidget(self.name, 0, 0)
        grid.addWidget(self.name_edit, 0, 1)
        grid.addWidget(self.name_set, 0, 2)
        grid.addWidget(self.name_button, 0, 3)

        grid.addWidget(self.elements, 2, 0)
        grid.addWidget(self.elements_edit, 2, 1)
        grid.addWidget(self.elements_button, 2, 2)

        grid.addWidget(self.add, 4, 0)
        grid.addWidget(self.add_edit, 4, 1)
        grid.addWidget(self.add_button, 4, 2)

        grid.addWidget(self.remove, 5, 0)
        grid.addWidget(self.remove_edit, 5, 1)
        grid.addWidget(self.remove_button, 5, 2)

        ok_cancel_box = QHBoxLayout()
        #ok_cancel_box.addWidget(self.apply_button)
        ok_cancel_box.addWidget(self.ok_button)
        #ok_cancel_box.addWidget(self.cancel_button)

        main_create_delete = QHBoxLayout()
        main_create_delete.addWidget(self.set_as_main_button)
        main_create_delete.addWidget(self.create_group_button)
        main_create_delete.addWidget(self.delete_group_button)

        vbox = QVBoxLayout()
        vbox.addWidget(self.table)
        vbox.addLayout(grid)
        vbox.addLayout(main_create_delete)
        vbox.addStretch()
        vbox.addLayout(ok_cancel_box)
        self.setLayout(vbox)

    def on_set_name(self):
        name = str(self.name_edit.text()).strip()
        if name not in self.keys:
            self.name_edit.setStyleSheet("QLineEdit{background: white;}")
            group = self.out_data[self.active_key]
            group.name = name
            self.keys[self.active_key] = name
            self.recreate_table()
        elif name != self.keys[self.active_key]:
            self.name_edit.setStyleSheet("QLineEdit{background: red;}")
        elif name == self.keys[self.active_key]:
            self.name_edit.setStyleSheet("QLineEdit{background: white;}")

    def set_connections(self):
        self.name_set.clicked.connect(self.on_set_name)
        self.name_button.clicked.connect(self.on_default_name)
        self.elements_button.clicked.connect(self.on_default_elements)

        self.add_button.clicked.connect(self.on_add)
        self.remove_button.clicked.connect(self.on_remove)
        self.table.itemClicked.connect(self.on_update_active_key)
        self.ok_button.clicked.connect(self.on_ok)

        self.set_as_main_button.clicked.connect(self.on_set_as_main)
        self.create_group_button.clicked.connect(self.on_create_group)
        self.delete_group_button.clicked.connect(self.on_delete_group)

    def on_create_group(self):
        irow = self.nrows
        new_key = 'Group %s' % irow
        while new_key in self.keys:
            irow += 1
            new_key = 'Group %s' % irow
        irow = self.nrows

        self.keys.append(new_key)
        group = Group(new_key,
                      element_str='',
                      elements_pound=self.elements_pound,
                      editable=True)
        self.out_data[irow] = group

        self.table.reset()
        self.table.addItems(self.keys)
        self.nrows += 1

        #----------------------------------
        # update internal parameters
        #self.out_data = items
        if self.imain > self.active_key:
            self.imain += 1

        #make the new group the default
        self.active_key = self.nrows - 1

        self.keys = [
            group.name for key, group in sorted(iteritems(self.out_data))
            if isinstance(key, int)
        ]
        self.recreate_table()

    def recreate_table(self):
        # update gui
        self.table.clear()
        self.table.addItems(self.keys)
        item = self.table.item(self.imain)

        bold = QtGui.QFont()
        bold.setBold(True)
        bold.setItalic(True)
        item.setFont(bold)
        self.table.update()

        # update key
        name = self.keys[self.active_key]
        self._update_active_key_by_name(name)

    def on_delete_group(self):
        if self.active_key == 0:
            return

        #self.deleted_groups.add(self.imain)
        items = {}
        j = 0
        for i, key in sorted(iteritems(self.out_data)):
            if isinstance(i, int):
                continue
            if i != self.active_key:
                items[j] = key
                j += 1

        # update internal parameters
        self.out_data = items
        if self.imain >= self.active_key:
            self.imain = max(0, self.imain - 1)
        self.active_key = max(0, self.active_key - 1)
        self.nrows -= 1
        self.keys = [group.name for key, group in sorted(iteritems(items))]

        self.recreate_table()

        # update key
        name = self.keys[self.active_key]
        self._update_active_key_by_name(name)

    def on_set_as_main(self):
        bold = QtGui.QFont()
        bold.setBold(True)
        bold.setItalic(True)

        normal = QtGui.QFont()
        normal.setBold(False)
        normal.setItalic(False)

        obj = self.table.item(self.imain)
        obj.setFont(normal)

        self.imain = self.active_key
        obj = self.table.item(self.imain)
        obj.setFont(bold)
        group = self.out_data[self.imain]
        self._default_elements = group.element_str
        self._default_name = group.name
        self.on_update_main()

    def on_update_main(self):
        """adds/removes the elements to the main actor when add/remove is pressed"""
        group = self.out_data[self.imain]
        if self._default_name == group.name and self.win_parent is not None:
            # we're not testing the menu
            self.win_parent.post_group(group)

    def closeEvent(self, event):
        self.out_data['close'] = True
        event.accept()

    def on_add(self):
        eids, is_valid = self.check_patran_syntax(self.add_edit,
                                                  pound=self.elements_pound)
        #adict, is_valid = self.check_patran_syntax_dict(self.add_edit)
        if not is_valid:
            #self.add_edit.setStyleSheet("QLineEdit{background: red;}")
            return

        self.eids = unique(hstack([self.eids, eids]))
        #self.eids = _add(adict, ['e', 'elem', 'element'], self.eids)
        #self.cids = _add(adict, ['c', 'cid', 'coord'], self.cids)
        self._apply_cids_eids()

        self.add_edit.clear()
        self.add_edit.setStyleSheet("QLineEdit{background: white;}")
        self.on_update_main()

    def _apply_cids_eids(self):
        #ctext = _get_collapsed_text(self.cids)
        etext = _get_collapsed_text(self.eids)

        #self.coords_edit.setText(str(ctext.lstrip()))
        self.elements_edit.setText(str(etext.lstrip()))
        self.out_data[self.active_key].element_ids = self.eids

    def on_remove(self):
        eids, is_valid = self.check_patran_syntax(self.remove_edit)
        #adict, is_valid = self.check_patran_syntax_dict(self.remove_edit)
        if not is_valid:
            #self.remove_edit.setStyleSheet("QLineEdit{background: red;}")
            return

        #self.eids = _remove(adict, ['e', 'elem', 'element'], self.eids)
        #self.cids = _remove(adict, ['c', 'cid', 'coord'], self.cids)
        self.eids = setdiff1d(self.eids, eids)
        self._apply_cids_eids()

        self.remove_edit.clear()
        self.remove_edit.setStyleSheet("QLineEdit{background: white;}")
        self.on_update_main()

    def on_default_name(self):
        name = str(self._default_name)
        self.name_edit.setText(name)
        self.name_edit.setStyleSheet("QLineEdit{background: white;}")

    def on_default_elements(self):
        element_str = str(self._default_elements)
        self.elements_edit.setText(element_str)
        self.elements_edit.setStyleSheet("QLineEdit{background: white;}")
        group = self.out_data[self.active_key]
        group.element_str = element_str

    def check_name(self, cell):
        cell_value = cell.text()
        try:
            text = str(cell_value).strip()
        except UnicodeEncodeError:
            cell.setStyleSheet("QLineEdit{background: red;}")
            return None, False

        if len(text):
            cell.setStyleSheet("QLineEdit{background: white;}")
            return text, True
        else:
            cell.setStyleSheet("QLineEdit{background: red;}")
            return None, False

        if self._default_name != text:
            if self._default_name in self.out_data:
                cell.setStyleSheet("QLineEdit{background: white;}")
                return text, True
            else:
                cell.setStyleSheet("QLineEdit{background: red;}")
                return None, False

    def on_validate(self):
        name, flag0 = self.check_name(self.name_edit)
        elements, flag1 = self.check_patran_syntax(self.elements_edit,
                                                   pound=self.elements_pound)
        #coords_value, flag2 = self.check_patran_syntax(self.coords_edit,
        #pound=self.coords_pound)

        if all([flag0, flag1]):
            self._default_name = name
            self._default_elements = self.eids
            self.out_data['clicked_ok'] = True
            self.out_data['close'] = True
            return True
        return False

    def on_apply(self, force=False):
        passed = self.on_validate()
        if passed or force:
            self.win_parent._apply_modify_groups(self.out_data)
        return passed

    def on_ok(self):
        passed = self.on_apply()
        if passed:
            self.close()
            #self.destroy()

    def on_cancel(self):
        self.out_data['close'] = True
        self.close()

    def on_update_active_key(self, index):
        self.update_active_key(index)
        #str(index.text())

    def update_active_key(self, index):
        #old_obj = self.out_data[self.imain]
        name = str(index.text())
        self._update_active_key_by_name(name)

    def _update_active_key_by_name(self, name):
        if name in self.keys:
            self.active_key = self.keys.index(name)
        else:
            # we (hopefully) just removed a row
            #self.active_key = self.keys[self.active_key]
            pass

        self.name_edit.setText(name)
        obj = self.out_data[self.active_key]

        self.eids = parse_patran_syntax(obj.element_str,
                                        pound=obj.elements_pound)
        self._default_elements = obj.element_str
        self._default_name = name
        self._apply_cids_eids()

        self.set_as_main_button.setEnabled(True)
        if name in ['main', 'anti-main']:
            self.name.setEnabled(False)
            self.name_set.setEnabled(False)
            self.name_edit.setEnabled(False)
            self.name_button.setEnabled(False)
            self.elements.setEnabled(False)
            self.elements_button.setEnabled(False)
            self.elements_edit.setEnabled(False)
            self.add.setEnabled(False)
            self.add_button.setEnabled(False)
            self.add_edit.setEnabled(False)
            self.remove.setEnabled(False)
            self.remove_button.setEnabled(False)
            self.remove_edit.setEnabled(False)
            self.delete_group_button.setEnabled(False)
            if name == 'anti-main':
                self.set_as_main_button.setEnabled(False)
            #self.apply_button.setEnabled(False)
            #self.ok_button.setEnabled(False)
        else:
            self.name.setEnabled(True)
            self.name_set.setEnabled(True)
            self.name_edit.setEnabled(True)
            self.name_button.setEnabled(True)
            self.elements.setEnabled(True)
            self.elements_button.setEnabled(True)

            self.add.setEnabled(True)
            self.add_button.setEnabled(True)
            self.add_edit.setEnabled(True)
            self.remove.setEnabled(True)
            self.remove_button.setEnabled(True)
            self.remove_edit.setEnabled(True)
            self.delete_group_button.setEnabled(True)
コード例 #10
0
class SelectPackages_Dialog(QDialog):
    """
    Now only used to state new paths to required packages that are not valid anymore.
    The 'auto import' feature is therefore probably useless by now.
    """
    def __init__(self, parent, required_packages: [str]):
        super(SelectPackages_Dialog, self).__init__(parent)

        self.file_paths = []
        self.required_packages: [str] = required_packages
        self.packages = []

        self.setLayout(QVBoxLayout())

        self.layout().addWidget(
            QLabel(
                'You need to select the locations of the following required node packages'
            ))

        # package lists
        required_packages_list_widget = QListWidget()
        for p_name in required_packages:
            required_packages_list_widget.addItem(QListWidgetItem(p_name))

        selected_items_widget = QWidget()
        selected_items_widget.setLayout(QVBoxLayout())
        self.selected_packages_list_widget = QListWidget()
        selected_items_widget.layout().addWidget(
            self.selected_packages_list_widget)

        auto_import_button = QPushButton('auto import')
        auto_import_button.setFocus()
        auto_import_button.clicked.connect(self.auto_import_button_clicked)
        selected_items_widget.layout().addWidget(auto_import_button)

        add_package_button = QPushButton('add')
        add_package_button.clicked.connect(self.add_package_button_clicked)
        selected_items_widget.layout().addWidget(add_package_button)

        clear_package_list_button = QPushButton('clear')
        clear_package_list_button.clicked.connect(
            self.clear_selected_packages_list)
        selected_items_widget.layout().addWidget(clear_package_list_button)

        finished_button = QPushButton('OK')
        finished_button.clicked.connect(self.finished_button_clicked)
        selected_items_widget.layout().addWidget(finished_button)

        packages_lists_widget = QWidget()
        packages_lists_widget.setLayout(QHBoxLayout())
        packages_lists_widget.layout().addWidget(required_packages_list_widget)
        packages_lists_widget.layout().addWidget(selected_items_widget)

        self.layout().addWidget(packages_lists_widget)

        self.setWindowTitle('select required packages')

    def auto_import_button_clicked(self):
        packages_dir = '../packages'
        folders_list = [
            basename(x[0]) for x in os.walk(packages_dir)
            if basename(x[0]) in self.required_packages
        ]

        required_packages = self.required_packages.copy()

        for pkg_name in required_packages:

            p_dir = join(packages_dir, pkg_name)
            p_file = normpath(join(p_dir, 'nodes.py'))

            if pkg_name in folders_list and \
                    'nodes.py' in os.listdir(p_dir) and \
                    p_file not in self.file_paths:

                self.file_paths.append(p_file)
                self.packages.append(NodesPackage(dirname(p_file)))

        self.rebuild_selected_packages_list_widget()

        self.clean_packages_list()

        if self.all_required_packages_selected():
            self.finished()

    def add_package_button_clicked(self):

        file_names = QFileDialog.getOpenFileNames(
            self, 'select package file (nodes.py)', '../packages', '(*.py)')[0]

        for file_name in file_names:
            try:
                # simply try to open the file to make sure it's valid
                f = open(file_name)
                f.close()

                self.file_paths.append(file_name)
                self.packages.append(NodesPackage(dirname(file_name)))

            except FileNotFoundError:
                pass

        self.rebuild_selected_packages_list_widget()

    def rebuild_selected_packages_list_widget(self):
        # remove all items
        self.selected_packages_list_widget.clear()

        for f in self.file_paths:
            file_item = QListWidgetItem()
            file_item.setText(f)
            self.selected_packages_list_widget.addItem(file_item)

    def clear_selected_packages_list(self):
        self.file_paths.clear()
        self.packages.clear()

        self.rebuild_selected_packages_list_widget()

    def finished_button_clicked(self):
        self.clean_packages_list()
        if self.all_required_packages_selected():
            self.finished()

    def clean_packages_list(self):
        """remove duplicates from self.file_paths"""

        files_dict = {}

        for p in self.file_paths:
            package_name = basename(dirname(p))
            files_dict[package_name] = p

        self.file_paths = list(files_dict.values())

        self.rebuild_selected_packages_list_widget()

    def all_required_packages_selected(self):

        selected_packages = [
            basename(normpath(np.directory)) for np in self.packages
        ]

        # search for missing packages
        for p in self.required_packages:
            if p not in selected_packages:
                return False

        return True

    def finished(self):
        self.accept()

    def closeEvent(self, arg__1) -> None:
        sys.exit()
コード例 #11
0
class SegmentationInfoDialog(QWidget):
    def __init__(self,
                 settings: StackSettings,
                 set_parameters: Callable[[str, dict], None],
                 additional_text=None):
        """

        :param settings:
        :param set_parameters: Function which set parameters of chosen in dialog.
        :param additional_text: Additional text on top of Window.
        """
        super().__init__()
        self.settings = settings
        self.parameters_dict = None
        self.set_parameters = set_parameters
        self.components = QListWidget()
        self.components.currentItemChanged.connect(self.change_component_info)
        self.description = QPlainTextEdit()
        self.description.setReadOnly(True)
        self.close_btn = QPushButton("Close")
        self.close_btn.clicked.connect(self.close)
        self.set_parameters_btn = QPushButton("Reuse parameters")
        self.set_parameters_btn.clicked.connect(self.set_parameter_action)
        self.additional_text_label = QLabel(additional_text)
        layout = QGridLayout()
        layout.addWidget(self.additional_text_label, 0, 0, 1, 2)
        if not additional_text:
            self.additional_text_label.setVisible(False)

        layout.addWidget(QLabel("Components:"), 1, 0)
        layout.addWidget(QLabel("segmentation parameters:"), 1, 1)
        layout.addWidget(self.components, 2, 0)
        layout.addWidget(self.description, 2, 1)
        layout.addWidget(self.close_btn, 3, 0)
        layout.addWidget(self.set_parameters_btn, 3, 1)
        self.setLayout(layout)
        self.setWindowTitle("Parameters preview")

    def set_parameters_dict(self, val: Optional[Dict[int,
                                                     SegmentationProfile]]):
        self.parameters_dict = val

    def set_additional_text(self, text):
        self.additional_text_label.setText(text)
        self.additional_text_label.setVisible(bool(text))

    @property
    def get_parameters(self):
        if self.parameters_dict:
            return self.parameters_dict
        return self.settings.components_parameters_dict

    def change_component_info(self):
        if self.components.currentItem() is None:
            return
        text = self.components.currentItem().text()
        parameters = self.get_parameters[int(text)]
        if parameters is None:
            self.description.setPlainText("None")
        else:
            self.description.setPlainText(
                f"Component {text}\n" +
                parameters.pretty_print(mask_algorithm_dict))

    def set_parameter_action(self):
        if self.components.currentItem() is None:
            return
        text = self.components.currentItem().text()
        parameters = self.get_parameters[int(text)]
        self.set_parameters(parameters.algorithm, parameters.values)

    def event(self, event: QEvent):
        if event.type() == QEvent.WindowActivate:
            index = self.components.currentRow()
            self.components.clear()
            self.components.addItems(list(map(str,
                                              self.get_parameters.keys())))
            self.components.setCurrentRow(index)
        return super().event(event)
コード例 #12
0
class CalculateInfo(QWidget):
    """
    "widget to show information about plans and allow to se plan details
    :type settings: Settings
    """

    plan_to_edit_signal = Signal()

    def __init__(self, settings: PartSettings):
        super(CalculateInfo, self).__init__()
        self.settings = settings
        self.calculate_plans = QListWidget(self)
        self.plan_view = PlanPreview(self)
        self.delete_plan_btn = QPushButton("Delete")
        self.edit_plan_btn = QPushButton("Edit")
        self.export_plans_btn = QPushButton("Export")
        self.import_plans_btn = QPushButton("Import")
        info_layout = QVBoxLayout()
        info_butt_layout = QGridLayout()
        info_butt_layout.setSpacing(0)
        info_butt_layout.addWidget(self.delete_plan_btn, 1, 1)
        info_butt_layout.addWidget(self.edit_plan_btn, 0, 1)
        info_butt_layout.addWidget(self.export_plans_btn, 1, 0)
        info_butt_layout.addWidget(self.import_plans_btn, 0, 0)
        info_layout.addLayout(info_butt_layout)
        info_chose_layout = QVBoxLayout()
        info_chose_layout.setSpacing(2)
        info_chose_layout.addWidget(QLabel("List of workflows:"))
        info_chose_layout.addWidget(self.calculate_plans)
        info_chose_layout.addWidget(QLabel("Preview:"))
        info_chose_layout.addWidget(self.plan_view)
        info_layout.addLayout(info_chose_layout)
        self.setLayout(info_layout)
        self.calculate_plans.addItems(
            list(sorted(self.settings.batch_plans.keys())))
        self.protect = False
        self.plan_to_edit = None

        self.plan_view.header().close()
        self.calculate_plans.currentTextChanged.connect(self.plan_preview)
        self.delete_plan_btn.clicked.connect(self.delete_plan)
        self.edit_plan_btn.clicked.connect(self.edit_plan)
        self.export_plans_btn.clicked.connect(self.export_plans)
        self.import_plans_btn.clicked.connect(self.import_plans)

    def update_plan_list(self):
        new_plan_list = list(sorted(self.settings.batch_plans.keys()))
        if self.calculate_plans.currentItem() is not None:
            text = str(self.calculate_plans.currentItem().text())
            try:
                index = new_plan_list.index(text)
            except ValueError:
                index = -1
        else:
            index = -1
        self.protect = True
        self.calculate_plans.clear()
        self.calculate_plans.addItems(new_plan_list)
        if index != -1:
            self.calculate_plans.setCurrentRow(index)
        else:
            pass
            # self.plan_view.setText("")
        self.protect = False

    def export_plans(self):
        choose = ExportDialog(self.settings.batch_plans, PlanPreview)
        if not choose.exec_():
            return
        dial = QFileDialog(self, "Export calculation plans")
        dial.setFileMode(QFileDialog.AnyFile)
        dial.setAcceptMode(QFileDialog.AcceptSave)
        dial.setDirectory(
            dial.setDirectory(
                self.settings.get("io.batch_plan_directory",
                                  str(Path.home()))))
        dial.setNameFilter("Calculation plans (*.json)")
        dial.setDefaultSuffix("json")
        dial.selectFile("calculation_plans.json")
        dial.setHistory(dial.history() + self.settings.get_path_history())
        if dial.exec_():
            file_path = str(dial.selectedFiles()[0])
            self.settings.set("io.batch_plan_directory",
                              os.path.dirname(file_path))
            self.settings.add_path_history(os.path.dirname(file_path))
            data = {
                x: self.settings.batch_plans[x]
                for x in choose.get_export_list()
            }
            with open(file_path, "w") as ff:
                json.dump(data,
                          ff,
                          cls=self.settings.json_encoder_class,
                          indent=2)

    def import_plans(self):
        dial = QFileDialog(self, "Import calculation plans")
        dial.setFileMode(QFileDialog.ExistingFile)
        dial.setAcceptMode(QFileDialog.AcceptOpen)
        dial.setDirectory(
            self.settings.get("io.open_directory", str(Path.home())))
        dial.setNameFilter("Calculation plans (*.json)")
        dial.setDefaultSuffix("json")
        dial.setHistory(dial.history() + self.settings.get_path_history())
        if dial.exec_():
            file_path = dial.selectedFiles()[0]
            plans, err = self.settings.load_part(file_path)
            self.settings.set("io.batch_plan_directory",
                              os.path.dirname(file_path))
            self.settings.add_path_history(os.path.dirname(file_path))
            if err:
                QMessageBox.warning(
                    self, "Import error",
                    "error during importing, part of data were filtered.")
            choose = ImportDialog(plans, self.settings.batch_plans,
                                  PlanPreview)
            if choose.exec_():
                for original_name, final_name in choose.get_import_list():
                    self.settings.batch_plans[final_name] = plans[
                        original_name]
                self.update_plan_list()

    def delete_plan(self):
        if self.calculate_plans.currentItem() is None:
            return
        text = str(self.calculate_plans.currentItem().text())
        if text == "":
            return
        if text in self.settings.batch_plans:
            del self.settings.batch_plans[text]
        self.update_plan_list()
        self.plan_view.clear()

    def edit_plan(self):
        if self.calculate_plans.currentItem() is None:
            return
        text = str(self.calculate_plans.currentItem().text())
        if text == "":
            return
        if text in self.settings.batch_plans:
            self.plan_to_edit = self.settings.batch_plans[text]
            self.plan_to_edit_signal.emit()

    def plan_preview(self, text):
        if self.protect:
            return
        text = str(text)
        if text.strip() == "":
            return
        plan = self.settings.batch_plans[str(text)]  # type: CalculationPlan
        self.plan_view.set_plan(plan)
コード例 #13
0
class AddFiles(QWidget):
    """Docstring for AddFiles. """

    file_list_changed = Signal(set)

    def __init__(self,
                 settings: BaseSettings,
                 parent=None,
                 btn_layout=QHBoxLayout):
        """TODO: to be defined1. """
        QWidget.__init__(self, parent)
        self.settings = settings
        self.files_to_proceed = set()
        self.paths = QLineEdit(self)
        self.selected_files = QListWidget(self)
        self.selected_files.itemSelectionChanged.connect(self.file_chosen)
        self.found_button = QPushButton("Find all", self)
        self.found_button.clicked.connect(self.find_all)
        self.select_files_button = QPushButton("Select files")
        self.select_dir_button = QPushButton("Select directory")
        self.select_files_button.clicked.connect(self.select_files)
        self.select_dir_button.clicked.connect(self.select_directory)
        self.delete_button = QPushButton("Remove file", self)
        self.delete_button.setDisabled(True)
        self.delete_button.clicked.connect(self.delete_element)
        self.clean_button = QPushButton("Remove all", self)
        self.clean_button.clicked.connect(self.clean)
        layout = QVBoxLayout()
        layout.addWidget(self.paths)
        select_layout = btn_layout()
        select_layout.addWidget(self.select_files_button)
        select_layout.addWidget(self.select_dir_button)
        select_layout.addWidget(self.found_button)
        select_layout.addStretch()
        select_layout.addWidget(self.clean_button)
        select_layout.addWidget(self.delete_button)
        layout.addLayout(select_layout)
        layout.addWidget(self.selected_files)
        self.setLayout(layout)

    def find_all(self):
        paths = glob(str(self.paths.text()))
        paths = sorted([
            x for x in (set(paths) - self.files_to_proceed)
            if not os.path.isdir(x)
        ])
        if len(paths) > 0:
            dialog = AcceptFiles(paths)
            if dialog.exec_():
                new_paths = dialog.get_files()
                for path in new_paths:
                    size = os.stat(path).st_size
                    size = float(size) / (1024**2)
                    lwi = QListWidgetItem("{:s} ({:.2f} MB)".format(
                        path, size))
                    lwi.setTextAlignment(Qt.AlignRight)
                    self.selected_files.addItem(lwi)
                self.files_to_proceed.update(new_paths)
                self.file_list_changed.emit(self.files_to_proceed)
        else:
            QMessageBox.warning(self, "No new files", "No new files found",
                                QMessageBox.Ok)

    def select_files(self):
        dial = QFileDialog(self, "Select files")
        dial.setDirectory(
            self.settings.get(
                "io.batch_directory",
                self.settings.get("io.load_image_directory",
                                  str(Path.home()))))
        dial.setFileMode(QFileDialog.ExistingFiles)
        if dial.exec_():
            self.settings.set("io.batch_directory",
                              os.path.dirname(str(dial.selectedFiles()[0])))
            new_paths = sorted(
                set(map(str, dial.selectedFiles())) - self.files_to_proceed)
            for path in new_paths:
                size = os.stat(path).st_size
                size = float(size) / (1024**2)
                lwi = QListWidgetItem("{:s} ({:.2f} MB)".format(path, size))
                lwi.setTextAlignment(Qt.AlignRight)
                self.selected_files.addItem(lwi)
            self.files_to_proceed.update(new_paths)
            self.file_list_changed.emit(self.files_to_proceed)

    def select_directory(self):
        dial = QFileDialog(self, "Select directory")
        dial.setDirectory(
            self.settings.get(
                "io.batch_directory",
                self.settings.get("io.load_image_directory",
                                  str(Path.home()))))
        dial.setFileMode(QFileDialog.Directory)
        if dial.exec_():
            self.paths.setText(dial.selectedFiles()[0])
            self.settings.set("io.batch_directory",
                              str(dial.selectedFiles()[0]))

    def file_chosen(self):
        self.delete_button.setEnabled(True)

    def delete_element(self):
        item = self.selected_files.takeItem(self.selected_files.currentRow())
        path = str(item.text())
        path = path[:path.rfind("(") - 1]
        self.files_to_proceed.remove(path)
        self.file_list_changed.emit(self.files_to_proceed)
        if self.selected_files.count() == 0:
            self.delete_button.setDisabled(True)

    def clean(self):
        self.selected_files.clear()
        self.files_to_proceed.clear()
        self.file_list_changed.emit(self.files_to_proceed)

    def get_paths(self):
        return list(sorted(self.files_to_proceed))
コード例 #14
0
ファイル: Window.py プロジェクト: desty2k/QtPyBotnet
class PayloadWindow(FramelessWindow):
    get_build_options = Signal()
    start_build = Signal(str, str, list)
    stop_build = Signal()

    def __init__(self, parent):
        super(PayloadWindow, self).__init__(parent)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.setContentsMargins(11, 11, 11, 11)
        self.progress_windows = []

        self.content_widget = QWidget(self)
        self.addContentWidget(self.content_widget)

        self.widget_layout = QFormLayout(self.content_widget)
        self.content_widget.setLayout(self.widget_layout)

        self.spinner = WaitingSpinner(self,
                                      modality=Qt.WindowModal,
                                      roundness=70.0,
                                      fade=70.0,
                                      radius=15.0,
                                      lines=6,
                                      line_length=25.0,
                                      line_width=4.0,
                                      speed=1.0)

        self.build_name_label = QLabel("Name", self.content_widget)
        self.build_name_edit = QLineEdit(self.content_widget)
        self.widget_layout.addRow(self.build_name_label, self.build_name_edit)

        self.icon_label = QLabel("Icon", self.content_widget)
        self.icon_combobox = QComboBox(self.content_widget)
        self.widget_layout.addRow(self.icon_label, self.icon_combobox)

        self.generators_label = QLabel("Generators", self.content_widget)
        self.generators_list = QListWidget(self.content_widget)
        self.widget_layout.addRow(self.generators_label, self.generators_list)

        self.build_button = QPushButton("Build", self.content_widget)
        self.build_button.clicked.connect(self.on_build_button_clicked)
        self.widget_layout.addWidget(self.build_button)

        self.menu = QMenu(self)
        self.menu.setTitle("Payload")
        self.reload_action = QAction(self.menu)
        self.reload_action.setText("Reload options")
        self.reload_action.triggered.connect(self.setupUi)
        self.menu.addAction(self.reload_action)
        self.addMenu(self.menu)

    @Slot()
    def setupUi(self):
        self.spinner.start()
        self.get_build_options.emit()

    @Slot(dict)
    def process_build_message(self, message):
        if self.isVisible():
            event = message.get("event")
            if event == "options":
                options = message.get("options")
                self.set_options(options.get("generators"),
                                 options.get("icons"))
            elif event == "started":
                self.set_started(message.get("generator_name"))
            elif event == "stopped":
                self.set_stopped()
            elif event == "progress":
                self.set_progress(message.get("generator_name"),
                                  message.get("progress"))
            elif event == "error":
                self.set_error(message.get("error"))
            elif event == "generator_finished":
                self.set_generator_finished(message.get("generator_name"),
                                            message.get("exit_code"))
            elif event == "build_finished":
                self.set_build_finished()

    @Slot(str, str)
    def set_progress(self, generator_name, progress):
        for win in self.progress_windows:
            if win.generator() == generator_name:
                win.appendProgress(progress)

    @Slot(str)
    def set_started(self, generator_name):
        win = ProgressWindow(self, generator_name)
        win.show()
        self.progress_windows.append(win)
        self.logger.info("Generator {} started!".format(generator_name))
        self.reload_action.setEnabled(False)
        self.spinner.start()

    @Slot(str, int)
    def set_generator_finished(self, generator_name, exit_code):
        self.logger.info("Generator {} finished with exit code {}.".format(
            generator_name, exit_code))

    @Slot(list, list)
    def set_options(self, generators, icons):
        self.build_name_edit.clear()
        self.generators_list.clear()
        self.icon_combobox.clear()
        for generator in generators:
            item = QListWidgetItem(generator, self.generators_list)
            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)
            self.generators_list.addItem(item)
        for icon in icons:
            name = icon.get("name")
            pix = QPixmap()
            pix.loadFromData(base64.b64decode(icon.get("ico")))
            ico = QIcon()
            ico.addPixmap(pix)
            self.icon_combobox.addItem(ico, name)
        self.spinner.stop()

    @Slot()
    def set_stopped(self):
        self.on_build_finished()
        self.build_button.setText("Build")
        self.stopped_messagebox = FramelessInformationMessageBox(self)
        self.stopped_messagebox.setText(
            "Build process has been stopped successfully.")
        self.stopped_messagebox.setStandardButtons(QDialogButtonBox.Ok)
        self.stopped_messagebox.button(QDialogButtonBox.Ok).clicked.connect(
            self.stopped_messagebox.close)
        self.stopped_messagebox.show()

    @Slot(str)
    def set_error(self, error):
        self.on_build_finished()
        self.build_button.setText("Build")
        self.error_messagebox = FramelessCriticalMessageBox(self)
        self.error_messagebox.setText(error)
        self.error_messagebox.setStandardButtons(QDialogButtonBox.Ok)
        self.error_messagebox.button(QDialogButtonBox.Ok).clicked.connect(
            self.error_messagebox.close)
        self.error_messagebox.show()

    @Slot()
    def set_build_finished(self):
        self.on_build_finished()
        self.build_button.setText("Build")
        self.build_finished_messagebox = FramelessInformationMessageBox(self)
        self.build_finished_messagebox.setText(
            "Build process has been finished.")
        self.build_finished_messagebox.setStandardButtons(QDialogButtonBox.Ok)
        self.build_finished_messagebox.button(
            QDialogButtonBox.Ok).clicked.connect(
                self.build_finished_messagebox.close)
        self.build_finished_messagebox.show()

    @Slot()
    def on_build_button_clicked(self):
        if self.build_button.text() == "Build":
            generators = []
            for i in range(self.generators_list.count()):
                item = self.generators_list.item(i)
                if item.checkState() == Qt.Checked:
                    generators.append(item.text())
            self.start_build.emit(self.build_name_edit.text(),
                                  self.icon_combobox.currentText(), generators)
            self.build_button.setText("Stop")
        else:
            self.stop_build_messagebox = FramelessQuestionMessageBox(self)
            self.stop_build_messagebox.setText(
                "Do you want to stop build process?")
            self.stop_build_messagebox.setStandardButtons(
                QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
            self.stop_build_messagebox.button(
                QDialogButtonBox.Cancel).clicked.connect(
                    self.stop_build_messagebox.close)
            self.stop_build_messagebox.button(
                QDialogButtonBox.Ok).clicked.connect(self.stop_build.emit)
            self.stop_build_messagebox.button(
                QDialogButtonBox.Ok).clicked.connect(
                    self.stop_build_messagebox.close)
            self.stop_build_messagebox.show()

    @Slot()
    def on_build_finished(self):
        self.reload_action.setEnabled(True)
        self.spinner.stop()

    @Slot()
    def close(self) -> bool:
        for win in self.progress_windows:
            win.close()
        return super().close()
コード例 #15
0
class GroupsModify(PyDialog):
    """
    +-------------------------------+
    |     Groups : Modify           |
    +-------------------------------+
    |                               |
    |  Name        xxx Default      |
    |  Nodes       xxx Default Show |
    |  Color       xxx Default      |
    |  Add         xxx Add     Show |
    |  Remove      xxx Remove  Show |
    |                               |
    |      Set  OK Cancel           |
    +-------------------------------+
    """
    def __init__(self,
                 data,
                 win_parent=None,
                 model_name=None,
                 group_active='main'):
        super(GroupsModify, self).__init__(data, win_parent)
        self.set_font_size(data['font_size'])
        self._updated_groups = False
        self.model_name = model_name
        self.actors = []

        #self.out_data = data

        #print(data)
        self.keys = get_groups_sorted_by_name(data)

        self.active_key = self.keys.index(group_active)

        group_obj = data[self.active_key]
        unused_name = group_obj.name

        self.imain = 0
        self.nrows = len(self.keys)

        self._default_name = group_obj.name
        self._default_elements = group_obj.element_str
        self.elements_pound = group_obj.elements_pound

        self.table = QListWidget(parent=None)
        self.table.clear()
        self.table.addItems(self.keys)

        self.setWindowTitle('Groups: Modify')
        self.create_widgets()
        self.create_layout()
        self.set_connections()

        self.on_set_as_main()

    def create_widgets(self):
        """creates the menu objects"""
        #icon = QtGui.QPixmap(os.path.join(ICON_PATH, 'node.png'))
        #icon = QtGui.QPixmap(os.path.join(ICON_PATH, 'element.png'))
        # Name
        self.pick_style_label = QLabel('Pick Style:')
        #self.pick_node_button = QPushButton('Node')
        self.pick_element_button = QPushButton('Element')
        self.pick_area_button = QPushButton('Area')
        #self.pick_node_button.setIcon(icon)
        #self.pick_area_button.setIcon(icon)

        # Name
        self.name_label = QLabel('Name:')
        self.name_set = QPushButton('Set')
        self.name_edit = QLineEdit(str(self._default_name).strip())
        self.name_button = QPushButton('Default')

        # elements
        self.elements_label = QLabel('Element IDs:')
        self.elements_edit = QLineEdit(str(self._default_elements).strip())
        self.elements_button = QPushButton('Default')
        self.elements_highlight_button = QPushButton('Show')

        # add
        self.add_label = QLabel('Add Elements:')
        self.add_edit = QElementEdit(self, self.model_name, pick_style='area')
        self.add_button = QPushButton('Add')
        self.add_highlight_button = QPushButton('Show')

        # remove
        self.remove_label = QLabel('Remove Elements:')
        self.remove_edit = QElementEdit(self,
                                        self.model_name,
                                        pick_style='area')
        self.remove_button = QPushButton('Remove')
        self.remove_highlight_button = QPushButton('Show')

        # applies a unique implicitly
        self.eids = parse_patran_syntax(str(self._default_elements),
                                        pound=self.elements_pound)

        # closing
        #self.apply_button = QPushButton('Apply')
        self.ok_button = QPushButton('Close')
        #self.cancel_button = QPushButton('Cancel')

        self.set_as_main_button = QPushButton('Set As Main')
        self.create_group_button = QPushButton('Create New Group')
        self.delete_group_button = QPushButton('Delete Group')

        self.name_label.setEnabled(False)
        self.name_set.setEnabled(False)
        self.name_edit.setEnabled(False)
        self.name_button.setEnabled(False)

        self.elements_label.setEnabled(False)
        self.elements_button.setEnabled(False)
        self.elements_edit.setEnabled(False)
        self.elements_highlight_button.setEnabled(False)

        self.add_label.setEnabled(False)
        self.add_button.setEnabled(False)
        self.add_edit.setEnabled(False)
        self.add_highlight_button.setEnabled(False)

        self.remove_label.setEnabled(False)
        self.remove_button.setEnabled(False)
        self.remove_edit.setEnabled(False)
        self.remove_highlight_button.setEnabled(False)

        #self.apply_button.setEnabled(False)
        #self.ok_button.setEnabled(False)

    def create_layout(self):
        """displays the menu objects"""
        hbox = QHBoxLayout()
        hbox.addWidget(self.pick_style_label)
        hbox.addWidget(self.pick_element_button)
        hbox.addWidget(self.pick_area_button)
        hbox.addStretch()

        grid = QGridLayout()
        irow = 0
        grid.addWidget(self.name_label, irow, 0)
        grid.addWidget(self.name_edit, irow, 1)
        grid.addWidget(self.name_set, irow, 2)
        grid.addWidget(self.name_button, irow, 3)
        irow += 1

        grid.addWidget(self.elements_label, irow, 0)
        grid.addWidget(self.elements_edit, irow, 1)
        grid.addWidget(self.elements_button, irow, 2)
        grid.addWidget(self.elements_highlight_button, irow, 3)
        irow += 1

        grid.addWidget(self.add_label, irow, 0)
        grid.addWidget(self.add_edit, irow, 1)
        grid.addWidget(self.add_button, irow, 2)
        grid.addWidget(self.add_highlight_button, irow, 3)
        irow += 1

        grid.addWidget(self.remove_label, irow, 0)
        grid.addWidget(self.remove_edit, irow, 1)
        grid.addWidget(self.remove_button, irow, 2)
        grid.addWidget(self.remove_highlight_button, irow, 3)
        irow += 1

        ok_cancel_box = QHBoxLayout()
        #ok_cancel_box.addWidget(self.apply_button)
        ok_cancel_box.addWidget(self.ok_button)
        #ok_cancel_box.addWidget(self.cancel_button)

        main_create_delete = QHBoxLayout()
        main_create_delete.addWidget(self.set_as_main_button)
        main_create_delete.addWidget(self.create_group_button)
        main_create_delete.addWidget(self.delete_group_button)

        vbox = QVBoxLayout()
        vbox.addWidget(self.table)
        vbox.addLayout(hbox)
        vbox.addLayout(grid)
        vbox.addLayout(main_create_delete)
        vbox.addStretch()
        vbox.addLayout(ok_cancel_box)
        self.setLayout(vbox)

    def on_set_name(self):
        self.remove_highlight_actor()
        name = str(self.name_edit.text()).strip()
        if name not in self.keys:
            self.name_edit.setStyleSheet("QLineEdit{background: white;}")
            group = self.out_data[self.active_key]
            group.name = name
            self.keys[self.active_key] = name
            self.recreate_table()
        elif name != self.keys[self.active_key]:
            self.name_edit.setStyleSheet('QLineEdit{background: red;}')
        elif name == self.keys[self.active_key]:
            self.name_edit.setStyleSheet('QLineEdit{background: white;}')

    def on_pick_element(self):
        self.add_edit.pick_style = 'single'
        self.remove_edit.pick_style = 'single'

    def on_pick_area(self):
        self.add_edit.pick_style = 'area'
        self.remove_edit.pick_style = 'area'

    def set_connections(self):
        """creates the actions for the menu"""
        self.pick_element_button.clicked.connect(self.on_pick_element)
        self.pick_area_button.clicked.connect(self.on_pick_area)

        self.name_set.clicked.connect(self.on_set_name)
        self.name_button.clicked.connect(self.on_default_name)
        self.elements_button.clicked.connect(self.on_default_elements)
        self.elements_highlight_button.clicked.connect(
            self.on_highlight_elements)

        self.add_button.clicked.connect(self.on_add)
        self.add_highlight_button.clicked.connect(self.on_highlight_add)

        self.remove_button.clicked.connect(self.on_remove)
        self.remove_highlight_button.clicked.connect(self.on_highlight_remove)

        self.table.itemClicked.connect(self.on_update_active_key)
        self.ok_button.clicked.connect(self.on_ok)

        self.set_as_main_button.clicked.connect(self.on_set_as_main)
        self.create_group_button.clicked.connect(self.on_create_group)
        self.delete_group_button.clicked.connect(self.on_delete_group)

    def on_create_group(self):
        self.remove_highlight_actor()

        irow = self.nrows
        new_key = f'Group {irow:d}'
        while new_key in self.keys:
            irow += 1
            new_key = f'Group {irow:d}'
        irow = self.nrows

        self.keys.append(new_key)
        group = Group(new_key,
                      element_str='',
                      elements_pound=self.elements_pound,
                      editable=True)
        self.out_data[irow] = group

        self.table.reset()
        self.table.addItems(self.keys)
        self.nrows += 1

        #----------------------------------
        # update internal parameters
        #self.out_data = items
        if self.imain > self.active_key:
            self.imain += 1

        #make the new group the default
        self.active_key = self.nrows - 1

        self.keys = get_groups_sorted_by_name(self.out_data)
        self.recreate_table()

    def recreate_table(self):
        # update gui
        self.table.clear()
        self.table.addItems(self.keys)
        item = self.table.item(self.imain)

        bold = QtGui.QFont()
        bold.setBold(True)
        bold.setItalic(True)
        item.setFont(bold)
        self.table.update()

        # update key
        name = self.keys[self.active_key]
        self._update_active_key_by_name(name)

    def on_delete_group(self):
        self.remove_highlight_actor()
        if self.active_key == 0:
            return

        #self.deleted_groups.add(self.imain)
        items = {}
        j = 0
        for i, key in sorted(self.out_data.items()):
            if isinstance(i, int):
                continue
            if i != self.active_key:
                items[j] = key
                j += 1

        # update internal parameters
        self.out_data = items
        if self.imain >= self.active_key:
            self.imain = max(0, self.imain - 1)
        self.active_key = max(0, self.active_key - 1)
        self.nrows -= 1
        self.keys = [group.name for key, group in sorted(items.items())]

        self.recreate_table()

        # update key
        name = self.keys[self.active_key]
        self._update_active_key_by_name(name)

    def on_set_as_main(self):
        bold = QtGui.QFont()
        bold.setBold(True)
        bold.setItalic(True)

        normal = QtGui.QFont()
        normal.setBold(False)
        normal.setItalic(False)

        obj = self.table.item(self.imain)
        obj.setFont(normal)

        self.imain = self.active_key
        obj = self.table.item(self.imain)
        obj.setFont(bold)
        group = self.out_data[self.imain]
        self._default_elements = group.element_str
        self._default_name = group.name
        self.on_update_main()

    def on_update_main(self):
        """adds/removes the elements to the main actor when add/remove is pressed"""
        group = self.out_data[self.imain]
        if self._default_name == group.name and self.win_parent is not None:
            # we're not testing the menu
            self.win_parent.post_group(group, update_groups=True)

    def closeEvent(self, event):
        """closes the window"""
        self.remove_highlight_actor()
        self.out_data['close'] = True
        event.accept()

    def remove_highlight_actor(self):
        """removes the highlighted actor"""
        gui = self.win_parent
        remove_actors_from_gui(gui, self.actors, render=True)
        self.actors = []

    def on_highlight(self, nids=None, eids=None):
        """highlights the nodes"""
        gui = self.win_parent
        unused_name = self.keys[self.active_key]

        if gui is not None:
            self.remove_highlight_actor()
            ## TODO: super strange; doesn't work...
            mouse_actions = gui.mouse_actions
            grid = mouse_actions.get_grid_selected(self.model_name)
            #all_nodes = mouse_actions.node_ids
            all_elements = mouse_actions.element_ids

            actors = create_highlighted_actors(gui,
                                               grid,
                                               all_nodes=None,
                                               nodes=nids,
                                               all_elements=all_elements,
                                               elements=eids,
                                               add_actors=False)
            if actors:
                add_actors_to_gui(gui, actors, render=True)
                self.actors = actors

    def on_highlight_elements(self):
        """highlights the active elements"""
        self.on_highlight(eids=self.eids)

    def on_highlight_add(self):
        """highlights the elements to add"""
        eids, is_valid = check_patran_syntax(self.add_edit,
                                             pound=self.elements_pound)
        if not is_valid:
            #self.add_edit.setStyleSheet("QLineEdit{background: red;}")
            return
        self.on_highlight(eids=eids)

    def on_highlight_remove(self):
        """highlights the elements to remove"""
        eids, is_valid = check_patran_syntax(self.remove_edit)
        if not is_valid:
            #self.remove_edit.setStyleSheet("QLineEdit{background: red;}")
            return
        self.on_highlight(eids=eids)

    def on_add(self):
        self.remove_highlight_actor()
        eids, is_valid = check_patran_syntax(self.add_edit,
                                             pound=self.elements_pound)
        #adict, is_valid = check_patran_syntax_dict(self.add_edit)
        if not is_valid:
            #self.add_edit.setStyleSheet("QLineEdit{background: red;}")
            return

        self.eids = unique(hstack([self.eids, eids]))
        #self.eids = _add(adict, ['e', 'elem', 'element'], self.eids)
        #self.cids = _add(adict, ['c', 'cid', 'coord'], self.cids)
        self._apply_cids_eids()

        self.add_edit.clear()
        self.add_edit.setStyleSheet("QLineEdit{background: white;}")
        self.on_update_main()

    def _apply_cids_eids(self):
        #ctext = _get_collapsed_text(self.cids)
        etext = _get_collapsed_text(self.eids)

        #self.coords_edit.setText(str(ctext.lstrip()))
        self.elements_edit.setText(str(etext.lstrip()))
        self.out_data[self.active_key].element_ids = self.eids

    def on_remove(self):
        self.remove_highlight_actor()
        eids, is_valid = check_patran_syntax(self.remove_edit)
        #adict, is_valid = check_patran_syntax_dict(self.remove_edit)
        if not is_valid:
            #self.remove_edit.setStyleSheet("QLineEdit{background: red;}")
            return

        #self.eids = _remove(adict, ['e', 'elem', 'element'], self.eids)
        #self.cids = _remove(adict, ['c', 'cid', 'coord'], self.cids)
        self.eids = setdiff1d(self.eids, eids)
        self._apply_cids_eids()

        self.remove_edit.clear()
        self.remove_edit.setStyleSheet(QLINE_EDIT_BASIC)
        self.on_update_main()

    def on_default_name(self):
        self.remove_highlight_actor()
        name = str(self._default_name)
        self.name_edit.setText(name)
        self.name_edit.setStyleSheet("QLineEdit{background: white;}")

    def on_default_elements(self):
        self.remove_highlight_actor()
        element_str = str(self._default_elements)
        self.elements_edit.setText(element_str)
        self.elements_edit.setStyleSheet("QLineEdit{background: white;}")
        group = self.out_data[self.active_key]
        group.element_str = element_str

    def check_name(self, cell):
        cell_value = cell.text()
        try:
            text = str(cell_value).strip()
        except UnicodeEncodeError:
            cell.setStyleSheet("QLineEdit{background: red;}")
            return None, False

        if len(text):
            cell.setStyleSheet("QLineEdit{background: white;}")
            return text, True
        else:
            cell.setStyleSheet("QLineEdit{background: red;}")
            return None, False

        if self._default_name != text:
            if self._default_name in self.out_data:
                cell.setStyleSheet("QLineEdit{background: white;}")
                return text, True
            else:
                cell.setStyleSheet("QLineEdit{background: red;}")
                return None, False

    def on_validate(self):
        name, flag0 = self.check_name(self.name_edit)
        unused_elements, flag1 = check_patran_syntax(self.elements_edit,
                                                     pound=self.elements_pound)
        #coords_value, flag2 = check_patran_syntax(
        #self.coords_edit, pound=self.coords_pound)

        if all([flag0, flag1]):
            self._default_name = name
            self._default_elements = self.eids
            self.out_data['clicked_ok'] = True
            self.out_data['close'] = True
            return True
        return False

    def on_apply(self, force=False):
        passed = self.on_validate()
        if passed or force:
            self.win_parent._apply_modify_groups(self.out_data)
        return passed

    def on_ok(self):
        passed = self.on_apply()
        if passed:
            self.close()
            #self.destroy()

    def on_cancel(self):
        self.remove_highlight_actor()
        self.out_data['close'] = True
        self.close()

    def on_update_active_key(self, index):
        self.update_active_key(index)
        #str(index.text())

    def update_active_key(self, index):
        #old_obj = self.out_data[self.imain]
        name = str(index.text())
        self._update_active_key_by_name(name)

    def _update_active_key_by_name(self, name):
        if name in self.keys:
            self.active_key = self.keys.index(name)
        else:
            # we (hopefully) just removed a row
            #self.active_key = self.keys[self.active_key]
            pass

        self.name_edit.setText(name)
        obj = self.out_data[self.active_key]

        self.eids = parse_patran_syntax(obj.element_str,
                                        pound=obj.elements_pound)
        self._default_elements = obj.element_str
        self._default_name = name
        self._apply_cids_eids()

        self.set_as_main_button.setEnabled(True)

        if name in ['main', 'anti-main']:
            enabled = False
            self.elements_edit.setEnabled(False)
            if name == 'anti-main':
                self.set_as_main_button.setEnabled(False)
            #self.apply_button.setEnabled(False)
            #self.ok_button.setEnabled(False)
        else:
            enabled = True
            self.elements_highlight_button.setEnabled(True)
            self.add_label.setEnabled(True)
            self.add_highlight_button.setEnabled(True)
            self.remove_highlight_button.setEnabled(True)
            #self.apply_button.setEnabled(True)
            #self.ok_button.setEnabled(True)

        self.name_label.setEnabled(enabled)
        self.name_set.setEnabled(enabled)
        self.name_edit.setEnabled(enabled)
        self.name_button.setEnabled(enabled)
        self.elements_label.setEnabled(enabled)
        self.elements_button.setEnabled(enabled)
        self.add_button.setEnabled(enabled)
        self.add_edit.setEnabled(enabled)

        self.remove_label.setEnabled(enabled)
        self.remove_button.setEnabled(enabled)
        self.remove_edit.setEnabled(enabled)
        self.delete_group_button.setEnabled(enabled)
コード例 #16
0
class RemoteKernelSetupDialog(QDialog):
    """Dialog to connect to existing kernels (either local or remote)."""

    def __init__(self, parent=None):
        super(RemoteKernelSetupDialog, self).__init__(parent)
        self.setWindowTitle(_('Setup remote kernel'))

        self.TEXT_FETCH_REMOTE_CONN_FILES_BTN = 'Fetch remote connection files'
        self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME = 'jupyter --runtime-dir'

        # Name of the connection
        cfg_name_label = QLabel(_('Configuration name:'))
        self.cfg_name_line_edit = QLineEdit()

        # SSH connection
        hostname_label = QLabel(_('Hostname:'))
        self.hostname_lineedit = QLineEdit()
        port_label = QLabel(_('Port:'))
        self.port_lineeidt = QLineEdit()
        self.port_lineeidt.setMaximumWidth(75)

        username_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:'))

        self.pw = QLineEdit()
        self.pw.setEchoMode(QLineEdit.Password)
        self.pw_radio.toggled.connect(self.pw.setEnabled)
        self.keyfile_radio.toggled.connect(self.pw.setDisabled)

        self.keyfile_path_lineedit = QLineEdit()
        keyfile_browse_btn = QPushButton(_('Browse'))
        keyfile_browse_btn.clicked.connect(self.select_ssh_key)
        keyfile_layout = QHBoxLayout()
        keyfile_layout.addWidget(self.keyfile_path_lineedit)
        keyfile_layout.addWidget(keyfile_browse_btn)

        passphrase_label = QLabel(_('Passphrase:'))
        self.passphrase_lineedit = QLineEdit()
        self.passphrase_lineedit.setPlaceholderText(_('Optional'))
        self.passphrase_lineedit.setEchoMode(QLineEdit.Password)

        self.keyfile_radio.toggled.connect(self.keyfile_path_lineedit.setEnabled)
        self.keyfile_radio.toggled.connect(self.passphrase_lineedit.setEnabled)
        self.keyfile_radio.toggled.connect(keyfile_browse_btn.setEnabled)
        self.keyfile_radio.toggled.connect(passphrase_label.setEnabled)
        self.pw_radio.toggled.connect(self.keyfile_path_lineedit.setDisabled)
        self.pw_radio.toggled.connect(self.passphrase_lineedit.setDisabled)
        self.pw_radio.toggled.connect(keyfile_browse_btn.setDisabled)
        self.pw_radio.toggled.connect(passphrase_label.setDisabled)

        # Button to fetch JSON files listing
        # self.kf_fetch_conn_files_btn = QPushButton(_(self.TEXT_FETCH_REMOTE_CONN_FILES_BTN))
        # self.kf_fetch_conn_files_btn.clicked.connect(self.fill_combobox_with_fetched_remote_connection_files)
        # self.cb_remote_conn_files = QComboBox()
        # self.cb_remote_conn_files.currentIndexChanged.connect(self._take_over_selected_remote_configuration_file)

        # Remote kernel groupbox
        self.start_remote_kernel_group = QGroupBox(_("Start remote kernel"))

        # Advanced settings to get remote connection files
        jupyter_runtime_location_cmd_label = QLabel(_('Command to get Jupyter runtime:'))
        self.jupyter_runtime_location_cmd_lineedit = QLineEdit()
        self.jupyter_runtime_location_cmd_lineedit.setPlaceholderText(_(self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME))

        # SSH layout
        ssh_layout = QGridLayout()
        ssh_layout.addWidget(cfg_name_label, 0, 0)
        ssh_layout.addWidget(self.cfg_name_line_edit, 0, 2)

        ssh_layout.addWidget(hostname_label, 1, 0, 1, 2)
        ssh_layout.addWidget(self.hostname_lineedit, 1, 2)
        ssh_layout.addWidget(port_label, 1, 3)
        ssh_layout.addWidget(self.port_lineeidt, 1, 4)
        ssh_layout.addWidget(username_label, 2, 0, 1, 2)
        ssh_layout.addWidget(self.username_lineedit, 2, 2, 1, 3)

        # SSH authentication layout
        auth_layout = QGridLayout()
        auth_layout.addWidget(self.pw_radio, 1, 0)
        auth_layout.addWidget(pw_label, 1, 1)
        auth_layout.addWidget(self.pw, 1, 2)
        auth_layout.addWidget(self.keyfile_radio, 2, 0)
        auth_layout.addWidget(keyfile_label, 2, 1)
        auth_layout.addLayout(keyfile_layout, 2, 2)
        auth_layout.addWidget(passphrase_label, 3, 1)
        auth_layout.addWidget(self.passphrase_lineedit, 3, 2)

        auth_layout.addWidget(jupyter_runtime_location_cmd_label, 4, 1)
        auth_layout.addWidget(self.jupyter_runtime_location_cmd_lineedit, 4, 2)
        # auth_layout.addWidget(self.kf_fetch_conn_files_btn, 5, 1)
        # auth_layout.addWidget(self.cb_remote_conn_files, 5, 2)

        auth_group.setLayout(auth_layout)

        # Remote kernel layout
        self.rm_group = QGroupBox(_("Setup up of a remote connection"))
        self.rm_group.setEnabled(False)
        rm_layout = QVBoxLayout()
        rm_layout.addLayout(ssh_layout)
        rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        rm_layout.addWidget(auth_group)
        self.rm_group.setLayout(rm_layout)
        self.rm_group.setCheckable(False)

        # Ok and Cancel buttons
        self.accept_btns = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            Qt.Horizontal, self)

        self.accept_btns.accepted.connect(self.accept)
        self.accept_btns.rejected.connect(self.reject)

        btns_layout = QHBoxLayout()
        btns_layout.addWidget(self.accept_btns)

        # Dialog layout
        layout = QVBoxLayout()
        layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8)))
        # layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12)))
        layout.addWidget(self.rm_group)
        layout.addLayout(btns_layout)

        # Main layout
        hbox_layout = QHBoxLayout(self)

        # Left side with the list of all remote connection configurations
        items_label = QLabel(text="Configured remote locations")
        self.items_list = QListWidget()
        self.items_list.clicked.connect(self._on_items_list_click)

        items_layout = QVBoxLayout()
        items_layout.addWidget(items_label)
        items_layout.addWidget(self.items_list)
        edit_delete_new_buttons_layout = QHBoxLayout()
        edit_btn = QPushButton(text="Edit")
        add_btn = QPushButton(text="Add")
        delete_btn = QPushButton(text="Delete")

        add_btn.clicked.connect(self._on_add_btn_click)
        edit_btn.clicked.connect(self._on_edit_btn_click)
        delete_btn.clicked.connect(self._on_delete_btn_click)

        edit_delete_new_buttons_layout.addWidget(add_btn)
        edit_delete_new_buttons_layout.addWidget(edit_btn)
        edit_delete_new_buttons_layout.addWidget(delete_btn)

        items_layout.addLayout(edit_delete_new_buttons_layout)

        hbox_layout.addSpacerItem(QSpacerItem(10, 0))
        hbox_layout.addLayout(items_layout)
        hbox_layout.addLayout(layout)

        self.lst_with_connecion_configs = []

    def _on_items_list_click(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings
        idx_of_config = self.items_list.selectedIndexes()[0].row()
        cfg = self.lst_with_connecion_configs[idx_of_config]
        if isinstance(cfg, RemoteConnectionSettings):
            self._update_remote_connection_input_fields(cfg)
        else:
            show_info_dialog("Information", "This functionality is still not available")

    def _clear_remote_connection_input_fields(self):
        self.keyfile_path_lineedit.setText("")
        self.passphrase_lineedit.setText("")
        self.hostname_lineedit.setText("")
        self.username_lineedit.setText("")
        self.port_lineeidt.setText("")
        self.cfg_name_line_edit.setText("")
        self.jupyter_runtime_location_cmd_lineedit.setText("")

        self.keyfile_radio.setChecked(False)
        self.pw_radio.setChecked(False)

    def _update_remote_connection_input_fields(self, remote_conn_settings):
        self.keyfile_path_lineedit.setText(remote_conn_settings.keyfile_path)
        self.passphrase_lineedit.setText(remote_conn_settings.password)
        self.hostname_lineedit.setText(remote_conn_settings.hostname)
        self.username_lineedit.setText(remote_conn_settings.username)
        self.port_lineeidt.setText(str(remote_conn_settings.port))
        self.cfg_name_line_edit.setText(remote_conn_settings.connection_name)
        self.jupyter_runtime_location_cmd_lineedit.setText(remote_conn_settings.cmd_for_jupyter_runtime_location)

        self.keyfile_radio.setChecked(remote_conn_settings.keyfile_path is not None)
        self.pw_radio.setChecked(remote_conn_settings.password is not None)

    def _on_add_btn_click(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings

        username = self.username_lineedit.text()
        passphrase = self.passphrase_lineedit.text()
        hostname = self.hostname_lineedit.text()
        keyfile_path = self.keyfile_path_lineedit.text()
        port = int(self.port_lineeidt.text()) if self.port_lineeidt.text() != "" else 22
        jup_runtime_cmd = self.jupyter_runtime_location_cmd_lineedit.text()
        cfg_name = self.cfg_name_line_edit.text()

        cfg = RemoteConnectionSettings(
            username=username,
            hostname=hostname,
            keyfile_path=keyfile_path,
            port=port,
            connection_name=cfg_name,
            cmd_for_jupyter_runtime_location=jup_runtime_cmd,
            password=passphrase
        )

        self.lst_with_connecion_configs.append(cfg)
        self._update_list_with_configs()
        self.rm_group.setEnabled(False)

    def _on_edit_btn_click(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings
        self.rm_group.setEnabled(True)
        idx_of_config = self.items_list.selectedIndexes()[0].row()
        cfg = self.lst_with_connecion_configs[idx_of_config]
        if isinstance(cfg, RemoteConnectionSettings):
            self._update_remote_connection_input_fields(cfg)
        else:
            show_info_dialog("Information", "This functionality is still not available")

    def _on_delete_btn_click(self):
        idx_of_config = self.items_list.selectedIndexes()[0].row()
        self.lst_with_connecion_configs.pop(idx_of_config)
        self._update_list_with_configs()

    def select_ssh_key(self):
        kf = getopenfilename(self, _('Select SSH keyfile'),
                             get_home_dir(), '*.pem;;*')[0]
        self.keyfile_path_lineedit.setText(kf)

    def _take_over_selected_remote_configuration_file(self, chosen_idx_of_combobox_with_remote_conn_files):
        remote_path_filename = self.remote_conn_file_paths[chosen_idx_of_combobox_with_remote_conn_files]
        self.cf.setText(remote_path_filename)

    def set_connection_configs(self, lst_with_connecion_configs):
        self.lst_with_connecion_configs = lst_with_connecion_configs
        self._update_list_with_configs()

    def _update_list_with_configs(self):
        from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings
        # now, fill the list
        self.items_list.clear()
        for cfg in self.lst_with_connecion_configs:
            if isinstance(cfg, LocalConnectionSettings):
                self.items_list.addItem(f"Local: {cfg.connection_name}")
            elif isinstance(cfg, RemoteConnectionSettings):
                self.items_list.addItem(f"Remote: {cfg.connection_name}")

    def get_connection_settings(self):
        return self.lst_with_connecion_configs
コード例 #17
0
ファイル: fileswitcher.py プロジェクト: rlaverde/spyder
class FileSwitcher(QDialog):
    """A Sublime-like file switcher."""
    sig_goto_file = Signal(int, object)

    # Constants that define the mode in which the list widget is working
    # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols
    # in a given file when using the '@' symbol.
    FILE_MODE, SYMBOL_MODE = [1, 2]
    MAX_WIDTH = 600

    def __init__(self, parent, plugin, tabs, data, icon):
        QDialog.__init__(self, parent)

        # Variables
        self.plugins_tabs = []
        self.plugins_data = []
        self.plugins_instances = []
        self.add_plugin(plugin, tabs, data, icon)
        self.plugin = None                # Last plugin with focus
        self.mode = self.FILE_MODE        # By default start in this mode
        self.initial_cursors = None       # {fullpath: QCursor}
        self.initial_path = None          # Fullpath of initial active editor
        self.initial_widget = None        # Initial active editor
        self.line_number = None           # Selected line number in filer
        self.is_visible = False           # Is the switcher visible?

        help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to "
                      "cancel.<br><br>Type to filter filenames.<br><br>"
                      "Use <b>:number</b> to go to a line, e.g. "
                      "<b><code>main:42</code></b><br>"
                      "Use <b>@symbol_text</b> to go to a symbol, e.g. "
                      "<b><code>@init</code></b>"
                      "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>")

        # Either allow searching for a line number or a symbol but not both
        regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" +
                        "([A-Za-z0-9_]{0,100}:{0,1}[0-9]{0,100})")

        # Widgets
        self.edit = FilesFilterLine(self)
        self.help = HelperToolButton()
        self.list = QListWidget(self)
        self.filter = KeyPressFilter()
        regex_validator = QRegExpValidator(regex, self.edit)

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setValidator(regex_validator)
        self.help.setToolTip(help_text)
        self.list.setItemDelegate(HTMLDelegate(self))

        # Layout
        edit_layout = QHBoxLayout()
        edit_layout.addWidget(self.edit)
        edit_layout.addWidget(self.help)
        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.rejected.connect(self.restore_initial_state)
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.edit.returnPressed.connect(self.accept)
        self.edit.textChanged.connect(self.setup)
        self.list.itemSelectionChanged.connect(self.item_selection_changed)
        self.list.clicked.connect(self.edit.setFocus)

    # --- Properties
    @property
    def widgets(self):
        widgets = []
        for tabs, plugin in self.plugins_tabs:
            widgets += [(tabs.widget(index), plugin) for
                        index in range(tabs.count())]
        return widgets

    @property
    def line_count(self):
        line_count = []
        for widget in self.widgets:
            try:
                current_line_count = widget[0].get_line_count()
            except AttributeError:
                current_line_count = 0
            line_count.append(current_line_count)
        return line_count

    @property
    def save_status(self):
        save_status = []
        for da, icon in self.plugins_data:
            save_status += [getattr(td, 'newly_created', False) for td in da]
        return save_status

    @property
    def paths(self):
        paths = []
        for da, icon in self.plugins_data:
            paths += [getattr(td, 'filename', None) for td in da]
        return paths

    @property
    def filenames(self):
        filenames = []
        for da, icon in self.plugins_data:
            filenames += [os.path.basename(getattr(td,
                                                   'filename',
                                                   None)) for td in da]
        return filenames

    @property
    def icons(self):
        icons = []
        for da, icon in self.plugins_data:
            icons += [icon for td in da]
        return icons

    @property
    def current_path(self):
        return self.paths_by_widget[self.get_widget()]

    @property
    def paths_by_widget(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(widgets, self.paths))

    @property
    def widgets_by_path(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(self.paths, widgets))

    @property
    def filter_text(self):
        """Get the normalized (lowecase) content of the filter text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, _str):
        self.edit.setText(_str)

    def save_initial_state(self):
        """Save initial cursors and initial active widget."""
        paths = self.paths
        self.initial_widget = self.get_widget()
        self.initial_cursors = {}

        for i, editor in enumerate(self.widgets):
            if editor is self.initial_widget:
                self.initial_path = paths[i]
            # This try is needed to make the fileswitcher work with 
            # plugins that does not have a textCursor.
            try:
                self.initial_cursors[paths[i]] = editor.textCursor()
            except AttributeError:
                pass

    def accept(self):
        self.is_visible = False
        QDialog.accept(self)
        self.list.clear()

    def restore_initial_state(self):
        """Restores initial cursors and initial active editor."""
        self.list.clear()
        self.is_visible = False
        widgets = self.widgets_by_path

        if not self.edit.clicked_outside:
            for path in self.initial_cursors:
                cursor = self.initial_cursors[path]
                if path in widgets:
                    self.set_editor_cursor(widgets[path], cursor)

            if self.initial_widget in self.paths_by_widget:
                index = self.paths.index(self.initial_path)
                self.sig_goto_file.emit(index)

    def set_dialog_position(self):
        """Positions the file switcher dialog."""
        parent = self.parent()
        geo = parent.geometry()
        width = self.list.width()  # This has been set in setup
        left = parent.geometry().width()/2 - width/2
        # Note: the +1 pixel on the top makes it look better
        if isinstance(parent, QMainWindow):
            top = (parent.toolbars_menu.geometry().height() +
                   parent.menuBar().geometry().height() + 1)
        else:
            top = self.plugins_tabs[0][0].tabBar().geometry().height() + 1

        while parent:
            geo = parent.geometry()
            top += geo.top()
            left += geo.left()
            parent = parent.parent()

        self.move(left, top)

    def get_item_size(self, content):
        """
        Get the max size (width and height) for the elements of a list of
        strings as a QLabel.
        """
        strings = []
        if content:
            for rich_text in content:
                label = QLabel(rich_text)
                label.setTextFormat(Qt.PlainText)
                strings.append(label.text())
                fm = label.fontMetrics()

            return (max([fm.width(s) * 1.3 for s in strings]), fm.height())

    def fix_size(self, content):
        """
        Adjusts the width and height of the file switcher
        based on the relative size of the parent and content.
        """
        # Update size of dialog based on relative size of the parent
        if content:
            width, height = self.get_item_size(content)

            # Width
            parent = self.parent()
            relative_width = parent.geometry().width() * 0.65
            if relative_width > self.MAX_WIDTH:
                relative_width = self.MAX_WIDTH
            self.list.setMinimumWidth(relative_width)

            # Height
            if len(content) < 8:
                max_entries = len(content)
            else:
                max_entries = 8
            max_height = height * max_entries * 2.5
            self.list.setMinimumHeight(max_height)

            # Resize
            self.list.resize(relative_width, self.list.height())

    # --- Helper methods: List widget
    def count(self):
        """Gets the item count in the list widget."""
        return self.list.count()

    def current_row(self):
        """Returns the current selected row in the list widget."""
        return self.list.currentRow()

    def set_current_row(self, row):
        """Sets the current selected row in the list widget."""
        return self.list.setCurrentRow(row)

    def select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def previous_row(self):
        """Select previous row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(-1)
            return
        prev_row = self.current_row() - 1
        if prev_row >= 0:
            title = self.list.item(prev_row).text()
        else:
            title = ''
        if prev_row == 0 and '</b></big><br>' in title:
            self.list.scrollToTop()
        elif '</b></big><br>' in title:
            # Select the next previous row, the one following is a title
            self.select_row(-2)
        else:
            self.select_row(-1)

    def next_row(self):
        """Select next row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(+1)
            return
        next_row = self.current_row() + 1
        if next_row < self.count():
            if '</b></big><br>' in self.list.item(next_row).text():
                # Select the next next row, the one following is a title
                self.select_row(+2)
            else:
                self.select_row(+1)

    def get_stack_index(self, stack_index, plugin_index):
        """Get the real index of the selected item."""
        other_plugins_count = sum([other_tabs[0].count() \
                                   for other_tabs in \
                                   self.plugins_tabs[:plugin_index]])
        real_index = stack_index - other_plugins_count

        return real_index

    # --- Helper methods: Widget
    def get_widget(self, index=None, path=None, tabs=None):
        """Get widget by index.
        
        If no tabs and index specified the current active widget is returned.
        """
        if index and tabs:
            return tabs.widget(index)
        elif path and tabs:
            return tabs.widget(index)
        elif self.plugin:
            index = self.plugins_instances.index(self.plugin)
            return self.plugins_tabs[index][0].currentWidget()
        else:
            return self.plugins_tabs[0][0].currentWidget()

    def set_editor_cursor(self, editor, cursor):
        """Set the cursor of an editor."""
        pos = cursor.position()
        anchor = cursor.anchor()

        new_cursor = QTextCursor()
        if pos == anchor:
            new_cursor.movePosition(pos)
        else:
            new_cursor.movePosition(anchor)
            new_cursor.movePosition(pos, QTextCursor.KeepAnchor)
        editor.setTextCursor(cursor)

    def goto_line(self, line_number):
        """Go to specified line number in current active editor."""
        if line_number:
            line_number = int(line_number)
            editor = self.get_widget()
            try:
                editor.go_to_line(min(line_number, editor.get_line_count()))
            except AttributeError:
                pass

    # --- Helper methods: Outline explorer
    def get_symbol_list(self):
        """Get the list of symbols present in the file."""
        try:
            oedata = self.get_widget().get_outlineexplorer_data()
        except AttributeError:
            oedata = {}
        return oedata

    # --- Handlers
    def item_selection_changed(self):
        """List widget item selection change handler."""
        row = self.current_row()
        if self.count() and row >= 0:
            if '</b></big><br>' in self.list.currentItem().text() and row == 0:
                self.next_row()
            if self.mode == self.FILE_MODE:
                try:
                    stack_index = self.paths.index(self.filtered_path[row])
                    self.plugin = self.widgets[stack_index][1]
                    plugin_index = self.plugins_instances.index(self.plugin)
                    # Count the real index in the tabWidget of the
                    # current plugin
                    real_index = self.get_stack_index(stack_index,
                                                      plugin_index)
                    self.sig_goto_file.emit(real_index,
                                            self.plugin.get_current_tab_manager())
                    self.goto_line(self.line_number)
                    try:
                        self.plugin.switch_to_plugin()
                        self.raise_()
                    except AttributeError:
                        # The widget using the fileswitcher is not a plugin
                        pass
                    self.edit.setFocus()
                except ValueError:
                    pass
            else:
                line_number = self.filtered_symbol_lines[row]
                self.goto_line(line_number)

    def setup_file_list(self, filter_text, current_path):
        """Setup list widget content for file list display."""
        short_paths = shorten_paths(self.paths, self.save_status)
        paths = self.paths
        icons = self.icons
        results = []
        trying_for_line_number = ':' in filter_text

        # Get optional line number
        if trying_for_line_number:
            filter_text, line_number = filter_text.split(':')
            # Get all the available filenames
            scores = get_search_scores('', self.filenames,
                                       template="<b>{0}</b>")
        else:
            line_number = None
            # Get all available filenames and get the scores for
            # "fuzzy" matching
            scores = get_search_scores(filter_text, self.filenames,
                                       template="<b>{0}</b>")


        # Get max width to determine if shortpaths should be used
        max_width = self.get_item_size(paths)[0]
        self.fix_size(paths)

        # Build the text that will appear on the list widget
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            if score_value != -1:
                text_item = '<big>' + rich_text.replace('&', '') + '</big>'
                if trying_for_line_number:
                    text_item += " [{0:} {1:}]".format(self.line_count[index],
                                                       _("lines"))
                if max_width > self.list.width():
                    text_item += u"<br><i>{0:}</i>".format(short_paths[index])
                else:
                    text_item += u"<br><i>{0:}</i>".format(paths[index])
                if (trying_for_line_number and self.line_count[index] != 0 or
                        not trying_for_line_number):
                    results.append((score_value, index, text_item))

        # Sort the obtained scores and populate the list widget
        self.filtered_path = []
        plugin = None
        for result in sorted(results):
            index = result[1]
            path = paths[index]
            icon = icons[index]
            text = ''
            try:
                title = self.widgets[index][1].get_plugin_title().split(' - ')
                if plugin != title[0]:
                    plugin = title[0]
                    text += '<br><big><b>' + plugin + '</b></big><br>'
                    item = QListWidgetItem(text)
                    item.setToolTip(path)
                    item.setSizeHint(QSize(0, 25))
                    item.setFlags(Qt.ItemIsEditable)
                    self.list.addItem(item)
                    self.filtered_path.append(path)
            except:
                # The widget using the fileswitcher is not a plugin
                pass
            text = ''
            text += result[-1]
            item = QListWidgetItem(icon, text)
            item.setToolTip(path)
            item.setSizeHint(QSize(0, 25))
            self.list.addItem(item)
            self.filtered_path.append(path)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = True

        # Move selected item in list accordingly and update list size
        if current_path in self.filtered_path:
            self.set_current_row(self.filtered_path.index(current_path))
        elif self.filtered_path:
            self.set_current_row(0)

        # If a line number is searched look for it
        self.line_number = line_number
        self.goto_line(line_number)

    def setup_symbol_list(self, filter_text, current_path):
        """Setup list widget content for symbol list display."""
        # Get optional symbol name
        filter_text, symbol_text = filter_text.split('@')

        # Fetch the Outline explorer data, get the icons and values
        oedata = self.get_symbol_list()
        icons = get_python_symbol_icons(oedata)

        # The list of paths here is needed in order to have the same
        # point of measurement for the list widget size as in the file list
        # See issue 4648
        paths = self.paths
        # Update list size
        self.fix_size(paths)

        symbol_list = process_python_symbol_data(oedata)
        line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list]
        choices = [item[1] for item in symbol_list]
        scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        results = []
        lines = []
        self.filtered_symbol_lines = []
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            line, fold_level, token = line_fold_token[index]
            lines.append(text)
            if score_value != -1:
                results.append((score_value, line, text, rich_text,
                                fold_level, icons[index], token))

        template = '{0}{1}'

        for (score, line, text, rich_text, fold_level, icon,
             token) in sorted(results):
            fold_space = '&nbsp;'*(fold_level)
            line_number = line + 1
            self.filtered_symbol_lines.append(line_number)
            textline = template.format(fold_space, rich_text)
            item = QListWidgetItem(icon, textline)
            item.setSizeHint(QSize(0, 16))
            self.list.addItem(item)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = False

        # Move selected item in list accordingly
        # NOTE: Doing this is causing two problems:
        # 1. It makes the cursor to auto-jump to the last selected
        #    symbol after opening or closing a different file
        # 2. It moves the cursor to the first symbol by default,
        #    which is very distracting.
        # That's why this line is commented!
        # self.set_current_row(0)

    def setup(self):
        """Setup list widget content."""
        if len(self.plugins_tabs) == 0:
            self.close()
            return

        self.list.clear()
        current_path = self.current_path
        filter_text = self.filter_text

        # Get optional line or symbol to define mode and method handler
        trying_for_symbol = ('@' in self.filter_text)

        if trying_for_symbol:
            self.mode = self.SYMBOL_MODE
            self.setup_symbol_list(filter_text, current_path)
        else:
            self.mode = self.FILE_MODE
            self.setup_file_list(filter_text, current_path)

        # Set position according to size
        self.set_dialog_position()

    def add_plugin(self, plugin, tabs, data, icon):
        """Add a plugin to display its files."""
        self.plugins_tabs.append((tabs, plugin))
        self.plugins_data.append((data, icon))
        self.plugins_instances.append(plugin)
コード例 #18
0
ファイル: preferences_dialog.py プロジェクト: wadhikar/napari
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
    }

    resized = Signal(QSize)
    closed = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def closeEvent(self, event):
        """Override to emit signal."""
        self.closed.emit()
        super().closeEvent(event)

    def reject(self):
        """Override to handle Escape."""
        super().reject()
        self.close()

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""

        # Because there are multiple pages, need to keep a dictionary of values dicts.
        # One set of keywords are for each page, then in each entry for a page, there are dicts
        # of setting and its value.

        self._values_orig_dict = {}
        self._values_dict = {}
        self._setting_changed_dict = {}

        for page, setting in SETTINGS.schemas().items():
            schema, values, properties = self.get_page_dict(setting)

            self._setting_changed_dict[page] = {}
            self._values_orig_dict[page] = values
            self._values_dict[page] = values

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def get_page_dict(self, setting):
        """Provides the schema, set of values for each setting, and the properties
        for each setting.

        Parameters
        ----------
        setting : dict
            Dictionary of settings for a page within the settings manager.

        Returns
        -------
        schema : dict
            Json schema of the setting page.
        values : dict
            Dictionary of values currently set for each parameter in the settings.
        properties : dict
            Dictionary of properties within the json schema.

        """

        schema = json.loads(setting['json_schema'])
        # Need to remove certain properties that will not be displayed on the GUI
        properties = schema.pop('properties')
        model = setting['model']
        values = model.dict()
        napari_config = getattr(model, "NapariConfig", None)
        if napari_config is not None:
            for val in napari_config.preferences_exclude:
                properties.pop(val)
                values.pop(val)

        schema['properties'] = properties

        return schema, values, properties

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            page = self._list.currentItem().text().split(" ")[0].lower()
            # get new values for settings.  If they were changed from values at beginning
            # of preference dialog session, change them back.
            # Using the settings value seems to be the best way to get the checkboxes right
            # on the plugin call order widget.
            setting = SETTINGS.schemas()[page]
            schema, new_values, properties = self.get_page_dict(setting)
            self.check_differences(self._values_orig_dict[page], new_values)

        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            d,
            self._values_dict[schema["title"].lower()],
        ))

        return form

    def _values_changed(self, page, new_dict, old_dict):
        """Loops through each setting in a page to determine if it changed.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting value
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the begining of preferences dialog session.

        """
        for setting_name, value in new_dict.items():
            if value != old_dict[setting_name]:
                self._setting_changed_dict[page][setting_name] = value
            elif (value == old_dict[setting_name]
                  and setting_name in self._setting_changed_dict[page]):
                self._setting_changed_dict[page].pop(setting_name)

    def check_differences(self, new_dict, old_dict):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_dict : dict
            Dict that has the most recent changes by user. Each key is a setting parameter
            and each item is the value.
        old_dict : dict
            Dict wtih values set at the beginning of the preferences dialog session.
        """
        page = self._list.currentItem().text().split(" ")[0].lower()
        self._values_changed(page, new_dict, old_dict)
        different_values = self._setting_changed_dict[page]

        if len(different_values) > 0:
            # change the values in SETTINGS
            for setting_name, value in different_values.items():
                try:
                    setattr(SETTINGS._settings[page], setting_name, value)
                    self._values_dict[page] = new_dict
                except:  # noqa: E722
                    continue
コード例 #19
0
ファイル: listwgt.py プロジェクト: hzyrc6011/pmgwidgets
class PMGListCtrl(BaseExtendedWidget):
    def __init__(self,
                 layout_dir: str,
                 title: str,
                 initial_value: List[List[str]],
                 new_id_func: Callable = None):
        super().__init__(layout_dir)
        self.choices = []
        self.text_list = []
        lab_title = QLabel(text=title)
        layout = QHBoxLayout()
        self.central_layout.addWidget(lab_title)
        self.on_check_callback = None
        self.list_widget = QListWidget()
        self.list_widget.mouseDoubleClickEvent = self.on_listwidget_double_cicked

        self.set_value(initial_value)
        layout_tools = QVBoxLayout()
        self.button_add_item = QPushButton('+')
        self.button_delete_item = QPushButton('-')
        self.button_delete_item.clicked.connect(self.delete_row)
        self.button_add_item.clicked.connect(self.add_row)
        self.button_add_item.setMaximumWidth(20)
        self.button_delete_item.setMaximumWidth(20)
        layout_tools.addWidget(self.button_add_item)
        layout_tools.addWidget(self.button_delete_item)
        layout.addLayout(layout_tools)
        layout.addWidget(self.list_widget)
        self.central_layout.addLayout(layout)
        self.data = initial_value
        self.new_id_func = new_id_func

        self.text_edit = QLineEdit(parent=self.list_widget)
        self.text_edit.setWindowFlags(self.text_edit.windowFlags() | Qt.Dialog
                                      | Qt.FramelessWindowHint)
        self.text_edit.hide()
        self.completer = QCompleter()
        self.text_edit.setCompleter(self.completer)
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)

    def set_completions(self, completions: List[str]):
        """
        设置补全内容
        Args:
            completions:

        Returns:

        """
        self.completer.setModel(QStringListModel(completions))

    def new_id(self):
        if callable(self.new_id_func):
            return self.new_id_func()
        else:
            return None

    def add_row(self):
        self.data = self.get_value()
        self.data[0].append(self.new_id())
        self.data[1].append('Unnamed')
        self.list_widget.addItem(QListWidgetItem('Unnamed'))

    def delete_row(self):
        index = self.list_widget.currentIndex().row()
        self.data[0].pop(index)
        self.data[1].pop(index)
        self.list_widget.takeItem(index)

    def on_listwidget_double_cicked(self, evt: QMouseEvent):
        print('edit', evt)
        pos = evt.globalPos()
        current_item: QListWidgetItem = self.list_widget.currentItem()

        def set_value():
            current_item.setText(self.text_edit.text())
            self.text_edit.hide()
            self.text_edit.returnPressed.disconnect(set_value)

        item: QListWidgetItem = self.list_widget.currentItem()

        self.text_edit.setGeometry(pos.x(), pos.y(), 200, 20)
        self.text_edit.returnPressed.connect(set_value)
        self.text_edit.show()
        # self.list_widget.editItem(item)

    def get_value(self):
        text = []
        for i in range(self.list_widget.count()):
            text.append(self.list_widget.item(i).text())
        self.data[1] = text
        assert len(self.data[1]) == len(self.data[0]), repr(self.data)
        return self.data

    def set_value(self, data: List[List[str]]):
        self.list_widget.clear()
        self.list_widget.addItems(data[1])
        self.data = data
        for index in range(self.list_widget.count()):
            item: QListWidgetItem = self.list_widget.item(index)
コード例 #20
0
class CycleWindow(SiriusMainWindow):
    """Power supplies cycle window."""
    def __init__(self, parent=None, checked_accs=(), adv_mode=False):
        """Constructor."""
        super().__init__(parent)
        self.setObjectName('ASApp')
        cor = get_appropriate_color(section='AS')
        self.setWindowIcon(qta.icon('mdi.recycle', color=cor))
        self._is_adv_mode = adv_mode
        # Data structs
        self._psnames = get_psnames(isadv=self._is_adv_mode)
        self._timing = Timing()
        self._ps2cycle = list()
        self._ps_ready = list()
        self._ps_failed = list()
        self._checked_accs = checked_accs
        # Flags
        self._is_preparing = ''
        self._prepared_init_vals = {
            'timing': False,
            'ps_sofbmode': False,
            'ps_om_slowref': False,
            'ps_current': False,
            'ps_params': False,
            'ps_om_cycle': False,
            'trims': True
        }
        self._prepared = self._prepared_init_vals.copy()
        self._icon_check = qta.icon('fa5s.check')
        self._pixmap_check = self._icon_check.pixmap(
            self._icon_check.actualSize(QSize(16, 16)))
        self._icon_not = qta.icon('fa5s.times')
        self._pixmap_not = self._icon_not.pixmap(
            self._icon_not.actualSize(QSize(16, 16)))
        # Tasks
        self._step_2_task = {
            'save_timing': SaveTiming,
            'timing': PrepareTiming,
            'ps_sofbmode': PreparePSSOFBMode,
            'ps_om_slowref': PreparePSOpModeSlowRef,
            'ps_current': PreparePSCurrentZero,
            'ps_params': PreparePSParams,
            'ps_om_cycle': PreparePSOpModeCycle,
            'trims': CycleTrims,
            'cycle': Cycle,
            'restore_timing': RestoreTiming,
        }
        # Setup UI
        self._needs_update_setup = False
        self._setup_ui()
        self._update_setup_timer = QTimer(self)
        self._update_setup_timer.timeout.connect(self._update_setup)
        self._update_setup_timer.setInterval(250)
        self._update_setup_timer.start()
        self.setWindowTitle('PS Cycle')

    def _setup_ui(self):
        # central widget
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)

        # tree
        gb_tree = QGroupBox('Select power supplies:')
        self.pwrsupplies_tree = PVNameTree(self._psnames, ('sec', 'mag_group'),
                                           tuple(), self)
        self.pwrsupplies_tree.tree.setHeaderHidden(True)
        self.pwrsupplies_tree.tree.setColumnCount(1)
        glay_tree = QVBoxLayout(gb_tree)
        glay_tree.addWidget(self.pwrsupplies_tree)

        # commands
        lb_prep_ti = QLabel('<h4>Prepare Timing</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        ti_ch = [
            PVName(name).substitute(prefix=VACA_PREFIX)
            for name in self._timing.get_pvnames_by_psnames()
        ]
        self.ticonn_led = PyDMLedMultiConn(self, channels=ti_ch)

        self.save_timing_bt = QPushButton('1. Save Timing Initial State', self)
        self.save_timing_bt.setToolTip(
            'Save timing current state as initial state.')
        self.save_timing_bt.clicked.connect(
            _part(self._run_task, 'save_timing'))
        self.save_timing_bt.clicked.connect(self._set_lastcomm)

        self.prepare_timing_bt = QPushButton('2. Prepare Timing', self)
        self.prepare_timing_bt.setToolTip('Prepare EVG, triggers and events')
        self.prepare_timing_bt.clicked.connect(_part(self._run_task, 'timing'))
        self.prepare_timing_bt.clicked.connect(self._set_lastcomm)

        self.prepare_timing_lb = QLabel(self)
        self.prepare_timing_lb.setPixmap(self._pixmap_not)

        lb_prep_ps = QLabel('<h4>Prepare PS</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        self.psconn_led = PyDMLedMultiConn(self)

        self.set_ps_sofbmode_off_bt = QPushButton('3. Turn off PS SOFBMode',
                                                  self)
        self.set_ps_sofbmode_off_bt.setToolTip(
            'Turn off power supplies SOFBMode.')
        self.set_ps_sofbmode_off_bt.clicked.connect(
            _part(self._run_task, 'ps_sofbmode'))
        self.set_ps_sofbmode_off_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_sofbmode_off_lb = QLabel(self)
        self.set_ps_sofbmode_off_lb.setPixmap(self._pixmap_not)

        self.set_ps_opmode_slowref_bt = QPushButton(
            '4. Set PS OpMode to SlowRef', self)
        self.set_ps_opmode_slowref_bt.setToolTip(
            'Set power supplies OpMode to SlowRef.')
        self.set_ps_opmode_slowref_bt.clicked.connect(
            _part(self._run_task, 'ps_om_slowref'))
        self.set_ps_opmode_slowref_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_opmode_slowref_lb = QLabel(self)
        self.set_ps_opmode_slowref_lb.setPixmap(self._pixmap_not)

        self.set_ps_current_zero_bt = QPushButton('5. Set PS current to zero',
                                                  self)
        self.set_ps_current_zero_bt.setToolTip(
            'Set power supplies current to zero.')
        self.set_ps_current_zero_bt.clicked.connect(
            _part(self._run_task, 'ps_current'))
        self.set_ps_current_zero_bt.clicked.connect(self._set_lastcomm)

        self.set_ps_current_zero_lb = QLabel(self)
        self.set_ps_current_zero_lb.setPixmap(self._pixmap_not)

        self.prepare_ps_params_bt = QPushButton('6. Prepare PS Parameters',
                                                self)
        self.prepare_ps_params_bt.setToolTip(
            'Check power supplies OpMode in SlowRef, check\n'
            'current is zero and configure cycle parameters.')
        self.prepare_ps_params_bt.clicked.connect(
            _part(self._run_task, 'ps_params'))
        self.prepare_ps_params_bt.clicked.connect(self._set_lastcomm)

        self.prepare_ps_params_lb = QLabel(self)
        self.prepare_ps_params_lb.setPixmap(self._pixmap_not)

        self.prepare_ps_opmode_bt = QPushButton('7. Prepare PS OpMode', self)
        self.prepare_ps_opmode_bt.setToolTip(
            'Set power supplies OpMode to Cycle.')
        self.prepare_ps_opmode_bt.clicked.connect(
            _part(self._run_task, 'ps_om_cycle'))
        self.prepare_ps_opmode_bt.clicked.connect(self._set_lastcomm)

        self.prepare_ps_opmode_lb = QLabel(self)
        self.prepare_ps_opmode_lb.setPixmap(self._pixmap_not)

        lb_cycle = QLabel('<h4>Cycle</h4>', self, alignment=Qt.AlignCenter)

        self.cycle_trims_bt = QPushButton('8. Cycle Trims', self)
        self.cycle_trims_bt.setToolTip(
            'Cycle trims:\nStep 1) CH, QS and QTrims\nStep 2) CV')
        self.cycle_trims_bt.clicked.connect(_part(self._run_task, 'trims'))
        self.cycle_trims_bt.clicked.connect(self._set_lastcomm)
        self.cycle_trims_bt.setVisible(False)

        self.cycle_trims_lb = QLabel(self)
        self.cycle_trims_lb.setPixmap(self._pixmap_check)
        self.cycle_trims_lb.setVisible(False)

        self.cycle_bt = QPushButton('8. Cycle', self)
        self.cycle_bt.setToolTip(
            'Check all configurations,\nenable triggers and run cycle.')
        self.cycle_bt.clicked.connect(_part(self._run_task, 'cycle'))
        self.cycle_bt.clicked.connect(self._set_lastcomm)
        self.cycle_bt.setEnabled(False)

        lb_rest_ti = QLabel('<h4>Restore Timing</h4>',
                            self,
                            alignment=Qt.AlignCenter)
        self.restore_timing_bt = QPushButton('9. Restore Timing Initial State',
                                             self)
        self.restore_timing_bt.setToolTip('Restore timing initial state.')
        self.restore_timing_bt.clicked.connect(
            _part(self._run_task, 'restore_timing'))
        self.restore_timing_bt.clicked.connect(self._set_lastcomm)

        self._prepared_labels = {
            'timing': self.prepare_timing_lb,
            'ps_sofbmode': self.set_ps_sofbmode_off_lb,
            'ps_om_slowref': self.set_ps_opmode_slowref_lb,
            'ps_current': self.set_ps_current_zero_lb,
            'ps_params': self.prepare_ps_params_lb,
            'ps_om_cycle': self.prepare_ps_opmode_lb,
            'trims': self.cycle_trims_lb
        }

        gb_commsts = QGroupBox()
        gb_commsts.setStyleSheet("""
            QPushButton{min-height:1.5em;}
            QLabel{qproperty-alignment: AlignCenter;}""")
        lay_commsts = QGridLayout(gb_commsts)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 0, 0, 1, 2)
        lay_commsts.addWidget(lb_prep_ti, 1, 0)
        lay_commsts.addWidget(self.ticonn_led, 1, 1)
        lay_commsts.addWidget(self.save_timing_bt, 2, 0)
        lay_commsts.addWidget(self.prepare_timing_bt, 3, 0)
        lay_commsts.addWidget(self.prepare_timing_lb, 3, 1)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 4, 0)
        lay_commsts.addWidget(lb_prep_ps, 5, 0)
        lay_commsts.addWidget(self.psconn_led, 5, 1)
        lay_commsts.addWidget(self.set_ps_sofbmode_off_bt, 6, 0)
        lay_commsts.addWidget(self.set_ps_sofbmode_off_lb, 6, 1)
        lay_commsts.addWidget(self.set_ps_opmode_slowref_bt, 7, 0)
        lay_commsts.addWidget(self.set_ps_opmode_slowref_lb, 7, 1)
        lay_commsts.addWidget(self.set_ps_current_zero_bt, 8, 0)
        lay_commsts.addWidget(self.set_ps_current_zero_lb, 8, 1)
        lay_commsts.addWidget(self.prepare_ps_params_bt, 9, 0)
        lay_commsts.addWidget(self.prepare_ps_params_lb, 9, 1)
        lay_commsts.addWidget(self.prepare_ps_opmode_bt, 10, 0)
        lay_commsts.addWidget(self.prepare_ps_opmode_lb, 10, 1)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 11, 0)
        lay_commsts.addWidget(lb_cycle, 12, 0)
        lay_commsts.addWidget(self.cycle_trims_bt, 13, 0)
        lay_commsts.addWidget(self.cycle_trims_lb, 13, 1)
        lay_commsts.addWidget(self.cycle_bt, 14, 0)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 15, 0)
        lay_commsts.addWidget(lb_rest_ti, 16, 0)
        lay_commsts.addWidget(self.restore_timing_bt, 17, 0)
        lay_commsts.addItem(
            QSpacerItem(1, 1, QSzPlcy.Ignored, QSzPlcy.Expanding), 18, 0)
        lay_commsts.setColumnStretch(0, 10)
        lay_commsts.setColumnStretch(1, 1)
        lay_commsts.setVerticalSpacing(12)
        lay_commsts.setHorizontalSpacing(6)

        self.label_lastcomm = QLabel('Last Command: ', self)
        self.clearhist_bt = QPushButton('Clear', self)
        self.clearhist_bt.clicked.connect(self._clear_lastcomm)
        lay_lc = QHBoxLayout()
        lay_lc.setContentsMargins(0, 0, 0, 0)
        lay_lc.addWidget(self.label_lastcomm, alignment=Qt.AlignLeft)
        lay_lc.addWidget(self.clearhist_bt, alignment=Qt.AlignRight)
        lay_lc.setStretch(0, 10)
        lay_lc.setStretch(1, 1)

        self.progress_list = QListWidget(self)
        self.progress_list.setObjectName('progresslist')
        self.progress_list.setStyleSheet('#progresslist{min-width:20em;}')
        self.progress_list.itemDoubleClicked.connect(self._open_ps_detail)
        self.progress_list.setSelectionMode(QAbstractItemView.MultiSelection)
        self.progress_list.setToolTip(
            'Select rows and press Ctrl+C to copy and Esc to deselect.')

        self.progress_bar = MyProgressBar(self)

        lay_log = QVBoxLayout()
        lay_log.addLayout(lay_lc)
        lay_log.addWidget(self.progress_list)
        lay_log.addWidget(self.progress_bar)

        # connect tree signals
        self.pwrsupplies_tree.tree.doubleClicked.connect(self._open_ps_detail)
        self.pwrsupplies_tree.tree.itemChanged.connect(
            self._handle_checked_items_changed)
        self.pwrsupplies_tree.check_requested_levels(self._checked_accs)

        # layout
        layout = QGridLayout()
        layout.setVerticalSpacing(10)
        layout.setHorizontalSpacing(10)
        layout.addWidget(
            QLabel('<h3>PS Cycle</h3>', self, alignment=Qt.AlignCenter), 0, 0,
            1, 3)
        layout.addWidget(gb_tree, 1, 0)
        layout.addWidget(gb_commsts, 1, 1)
        layout.addLayout(lay_log, 1, 2)
        layout.setRowStretch(0, 1)
        layout.setRowStretch(1, 15)
        layout.setColumnStretch(0, 5)
        layout.setColumnStretch(1, 4)
        layout.setColumnStretch(2, 8)
        self.central_widget.setLayout(layout)

    # --- handle tasks ---

    def _run_task(self, control=''):
        if not self._check_connected(control):
            return
        pwrsupplies = self._get_ps_list()
        if not pwrsupplies:
            return

        if 'ps' in control and not self._verify_ps(pwrsupplies):
            return

        if control in self._step_2_task:
            task_class = self._step_2_task[control]
        else:
            raise NotImplementedError(
                "Task not defined for control '{}'".format(control))
        self._is_preparing = control

        self._handle_buttons_enabled(False)
        self.progress_list.clear()

        task = task_class(parent=self,
                          psnames=pwrsupplies,
                          timing=self._timing,
                          isadv=self._is_adv_mode)
        task.updated.connect(self._update_progress)
        duration = task.duration()

        self.progress_bar.setMinimum(0)
        self.progress_bar.setMaximum(duration)
        self.progress_bar.setValue(0)
        pal = self.progress_bar.palette()
        pal.setColor(QPalette.Highlight, self.progress_bar.default_color)
        self.progress_bar.setPalette(pal)

        self.update_bar = UpdateProgressBar(duration, self)
        self.update_bar.increment.connect(self.progress_bar.increment)

        task.start()
        self.update_bar.start()

    def _update_progress(self, text, done, warning=False, error=False):
        """Update automated cycle progress list and bar."""
        if done:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            curr_text = last_item.text()
            last_item.setText(curr_text + ' done.')
        elif 'Remaining time' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Remaining time' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Sent ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Sent ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Successfully checked ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Successfully checked ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        elif 'Created connections ' in text:
            last_item = self.progress_list.item(self.progress_list.count() - 1)
            if 'Created connections ' in last_item.text():
                last_item.setText(text)
            else:
                self.progress_list.addItem(text)
                self.progress_list.scrollToBottom()
        else:
            item = QListWidgetItem(text)
            if error:
                item.setForeground(errorcolor)
                self.update_bar.exit_task()
                pal = self.progress_bar.palette()
                pal.setColor(QPalette.Highlight,
                             self.progress_bar.warning_color)
                self.progress_bar.setPalette(pal)
                if self._is_preparing in self._prepared.keys():
                    self._prepared[self._is_preparing] = False
                cycle = all(self._prepared.values())
                self._handle_buttons_enabled(True, cycle=cycle)
            elif warning:
                item.setForeground(warncolor)
            elif 'finished' in text:
                self.update_bar.exit_task()
                self.progress_bar.setValue(self.progress_bar.maximum())
                if self._is_preparing == 'cycle':
                    self._prepared = {k: False for k in self._prepared.keys()}
                    if not self.cycle_trims_bt.isVisible():
                        self._prepared['trims'] = True
                    cycle = False
                else:
                    if self._is_preparing in self._prepared.keys():
                        self._prepared[self._is_preparing] = True
                    cycle = all(self._prepared.values())
                self._handle_buttons_enabled(True, cycle=cycle)

            self._handle_stslabels_content()

            self.progress_list.addItem(item)
            self.progress_list.scrollToBottom()

    def _handle_buttons_enabled(self, enable, cycle=False):
        self.save_timing_bt.setEnabled(enable)
        self.prepare_timing_bt.setEnabled(enable)
        self.set_ps_sofbmode_off_bt.setEnabled(enable)
        self.set_ps_opmode_slowref_bt.setEnabled(enable)
        self.set_ps_current_zero_bt.setEnabled(enable)
        self.prepare_ps_params_bt.setEnabled(enable)
        self.prepare_ps_opmode_bt.setEnabled(enable)
        self.cycle_trims_bt.setEnabled(enable)
        self.cycle_bt.setEnabled(cycle)
        self.restore_timing_bt.setEnabled(enable)
        self.clearhist_bt.setEnabled(enable)
        self.pwrsupplies_tree.setEnabled(enable)

    def _handle_stslabels_content(self):
        for prep, value in self._prepared.items():
            pixmap = self._pixmap_check if value else self._pixmap_not
            self._prepared_labels[prep].setPixmap(pixmap)

    def _set_lastcomm(self):
        sender_text = self.sender().text()
        self.label_lastcomm.setText('Last Command: ' + sender_text)

    def _clear_lastcomm(self):
        self.progress_bar.setValue(0)
        self.progress_list.clear()
        self.label_lastcomm.setText('Last Command: ')

    # --- handle ps selection ---

    def _get_ps_list(self):
        """Return list of power supplies to cycle."""
        # Get power supplies list
        pwrsupplies = self.pwrsupplies_tree.checked_items()
        if not pwrsupplies:
            QMessageBox.critical(self, 'Message', 'No power supply selected!')
            return False

        sections = get_sections(pwrsupplies)
        if 'BO' in sections and len(sections) > 1:
            QMessageBox.critical(self, 'Error',
                                 'Can not cycle Booster with other sectors!')
            return False

        create_task = CreateCyclers(parent=self, psnames=pwrsupplies)
        dlg = ProgressDialog('Creating cycles...', create_task, self)
        ret = dlg.exec_()
        if ret == dlg.Rejected:
            return False

        return pwrsupplies

    def _handle_checked_items_changed(self, item):
        psname = PVName(item.data(0, Qt.DisplayRole))
        if not _re.match('.*-.*:.*-.*', psname):
            return

        if not self._is_adv_mode and psname.sec == 'SI' and \
                not psname.dev.startswith('FC'):
            psname2check = Filter.process_filters(self._psnames,
                                                  filters={
                                                      'sec': 'SI',
                                                      'dev': '(?!FC)'
                                                  })
            psname2check.remove(psname)
            state2set = item.checkState(0)
            self.pwrsupplies_tree.tree.blockSignals(True)
            for psn in psname2check:
                item2check = self.pwrsupplies_tree._item_map[psn]
                if item2check.checkState(0) != state2set:
                    item2check.setData(0, Qt.CheckStateRole, state2set)
            self.pwrsupplies_tree.tree.blockSignals(False)
        else:
            if (psname.sec in ['BO', 'SI'] and psname.dev in ['B', 'B1B2']):
                psname2check = PSSearch.get_psnames({
                    'sec': psname.sec,
                    'dev': 'B.*'
                })
                psname2check.remove(psname)
                item2check = self.pwrsupplies_tree._item_map[psname2check[0]]

                state2set = item.checkState(0)
                state2change = item2check.checkState(0)
                if state2change != state2set:
                    self.pwrsupplies_tree.tree.blockSignals(True)
                    item2check.setData(0, Qt.CheckStateRole, state2set)
                    self.pwrsupplies_tree.tree.blockSignals(False)

        self._prepared.update(self._prepared_init_vals)
        self._needs_update_setup = True

    def _update_setup(self):
        if not self._needs_update_setup:
            return
        self._needs_update_setup = False

        # update leds
        psnames = self.pwrsupplies_tree.checked_items()
        ti_ch = [
            PVName(name).substitute(prefix=VACA_PREFIX)
            for name in self._timing.get_pvnames_by_psnames(psnames)
        ]
        self.ticonn_led.set_channels(ti_ch)

        ps_ch = list()
        for name in psnames:
            ps_ch.append(
                PVName(name).substitute(prefix=VACA_PREFIX,
                                        propty='PwrState-Sts'))
        self.psconn_led.set_channels(ps_ch)

        # update buttons and self._prepared dict if not in advanced mode
        if not self._is_adv_mode:
            has_si = False
            for psn in PSSearch.get_psnames({'sec': 'SI', 'dis': 'PS'}):
                if psn not in self.pwrsupplies_tree._item_map:
                    continue
                item = self.pwrsupplies_tree._item_map[psn]
                has_si |= item.checkState(0) != 0

            if not has_si:
                self.cycle_bt.setText('8. Cycle')
                self.restore_timing_bt.setText(
                    '9. Restore Timing Initial State')
                self.cycle_trims_bt.setVisible(False)
                self.cycle_trims_lb.setVisible(False)
                self._prepared['trims'] = True
            else:
                self.cycle_bt.setText('9. Cycle')
                self.restore_timing_bt.setText(
                    '10. Restore Timing Initial State')
                self.cycle_trims_bt.setVisible(True)
                self.cycle_trims_lb.setVisible(True)
                self._prepared['trims'] = False

        self._handle_stslabels_content()
        self._handle_buttons_enabled(True)

    # --- auxiliary checks ---

    def _check_connected(self, control):
        if control in ['trims', 'cycle']:
            leds = [self.ticonn_led, self.psconn_led]
        elif 'timing' in control:
            leds = [
                self.ticonn_led,
            ]
        else:
            leds = [
                self.psconn_led,
            ]

        for led in leds:
            pvs_disconnected = set()
            for ch, v in led.channels2conn.items():
                if not v:
                    pvs_disconnected.add(ch)
            if pvs_disconnected:
                sttr = ''
                for item in pvs_disconnected:
                    sttr += item + '\n'
                QMessageBox.information(
                    self, 'Message',
                    'The following PVs are not connected:\n' + sttr)
                return False
        return True

    def _verify_ps(self, pwrsupplies):
        self._ps_failed = set()

        task = VerifyPS(parent=self, psnames=pwrsupplies)
        task.itemDone.connect(self._get_ps_not_ready_2_cycle)

        dlg = ProgressDialog('Verifying power supplies initial state...', task,
                             self)
        ret = dlg.exec_()
        if ret == dlg.Rejected:
            self._handle_buttons_enabled(True)
            return False

        if self._ps_failed:
            text = 'Verify power state and interlocks' \
                ' of the following power supplies'
            dlg = PSStatusDialog(self._ps_failed, text, self)
            dlg.exec_()
            self._handle_buttons_enabled(True)
            return False

        return True

    def _get_ps_not_ready_2_cycle(self, psname, status):
        if not status:
            self._ps_failed.add(psname)

    def _open_ps_detail(self, item):
        if self.sender() == self.progress_list:
            text_split = item.data(Qt.DisplayRole).split(' ')
            psname = ''
            for text in text_split:
                if _re.match('.*-.*:.*-.*', text):
                    psname = text
            if not psname:
                return
        else:
            psname = item.data()
        if not _re.match('.*-.*:.*-.*', psname):
            if item.model().rowCount(item) == 1:
                psname = item.child(0, 0).data()
            else:
                return
        run_newprocess(['sirius-hla-as-ps-detail.py', psname])

    # --- events ---

    def keyPressEvent(self, evt):
        """Implement keyPressEvent."""
        if evt.matches(QKeySequence.Copy) and self.progress_list.underMouse():
            items = self.progress_list.selectedItems()
            items = '\n'.join([i.text() for i in items])
            QApplication.clipboard().setText(items)
        if evt.key() == Qt.Key_Escape and self.progress_list.underMouse():
            items = self.progress_list.clearSelection()
        super().keyPressEvent(evt)

    def closeEvent(self, ev):
        self._update_setup_timer.stop()
        super().closeEvent(ev)
コード例 #21
0
ファイル: pathmanager.py プロジェクト: rlaverde/spyder
class PathManager(QDialog):
    redirect_stdio = Signal(bool)
    
    def __init__(self, parent=None, pathlist=None, ro_pathlist=None,
                 not_active_pathlist=None, sync=True):
        QDialog.__init__(self, parent)
        
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        
        assert isinstance(pathlist, list)
        self.pathlist = pathlist
        if not_active_pathlist is None:
            not_active_pathlist = []
        self.not_active_pathlist = not_active_pathlist
        if ro_pathlist is None:
            ro_pathlist = []
        self.ro_pathlist = ro_pathlist
        
        self.last_path = getcwd()
        
        self.setWindowTitle(_("PYTHONPATH manager"))
        self.setWindowIcon(ima.icon('pythonpath'))
        self.resize(500, 300)
        
        self.selection_widgets = []
        
        layout = QVBoxLayout()
        self.setLayout(layout)
        
        top_layout = QHBoxLayout()
        layout.addLayout(top_layout)
        self.toolbar_widgets1 = self.setup_top_toolbar(top_layout)

        self.listwidget = QListWidget(self)
        self.listwidget.currentRowChanged.connect(self.refresh)
        self.listwidget.itemChanged.connect(self.update_not_active_pathlist)
        layout.addWidget(self.listwidget)

        bottom_layout = QHBoxLayout()
        layout.addLayout(bottom_layout)
        self.sync_button = None
        self.toolbar_widgets2 = self.setup_bottom_toolbar(bottom_layout, sync)        
        
        # Buttons configuration
        bbox = QDialogButtonBox(QDialogButtonBox.Close)
        bbox.rejected.connect(self.reject)
        bottom_layout.addWidget(bbox)
        
        self.update_list()
        self.refresh()

    @property
    def active_pathlist(self):
        return [path for path in self.pathlist
                if path not in self.not_active_pathlist]

    def _add_widgets_to_layout(self, layout, widgets):
        layout.setAlignment(Qt.AlignLeft)
        for widget in widgets:
            layout.addWidget(widget)
        
    def setup_top_toolbar(self, layout):
        toolbar = []
        movetop_button = create_toolbutton(self,
                                    text=_("Move to top"),
                                    icon=ima.icon('2uparrow'),
                                    triggered=lambda: self.move_to(absolute=0),
                                    text_beside_icon=True)
        toolbar.append(movetop_button)
        moveup_button = create_toolbutton(self,
                                    text=_("Move up"),
                                    icon=ima.icon('1uparrow'),
                                    triggered=lambda: self.move_to(relative=-1),
                                    text_beside_icon=True)
        toolbar.append(moveup_button)
        movedown_button = create_toolbutton(self,
                                    text=_("Move down"),
                                    icon=ima.icon('1downarrow'),
                                    triggered=lambda: self.move_to(relative=1),
                                    text_beside_icon=True)
        toolbar.append(movedown_button)
        movebottom_button = create_toolbutton(self,
                                    text=_("Move to bottom"),
                                    icon=ima.icon('2downarrow'),
                                    triggered=lambda: self.move_to(absolute=1),
                                    text_beside_icon=True)
        toolbar.append(movebottom_button)
        self.selection_widgets.extend(toolbar)
        self._add_widgets_to_layout(layout, toolbar)
        return toolbar
    
    def setup_bottom_toolbar(self, layout, sync=True):
        toolbar = []
        add_button = create_toolbutton(self, text=_('Add path'),
                                       icon=ima.icon('edit_add'),
                                       triggered=self.add_path,
                                       text_beside_icon=True)
        toolbar.append(add_button)
        remove_button = create_toolbutton(self, text=_('Remove path'),
                                          icon=ima.icon('edit_remove'),
                                          triggered=self.remove_path,
                                          text_beside_icon=True)
        toolbar.append(remove_button)
        self.selection_widgets.append(remove_button)
        self._add_widgets_to_layout(layout, toolbar)
        layout.addStretch(1)
        if os.name == 'nt' and sync:
            self.sync_button = create_toolbutton(self,
                  text=_("Synchronize..."),
                  icon=ima.icon('fileimport'), triggered=self.synchronize,
                  tip=_("Synchronize Spyder's path list with PYTHONPATH "
                              "environment variable"),
                  text_beside_icon=True)
            layout.addWidget(self.sync_button)
        return toolbar

    @Slot()
    def synchronize(self):
        """
        Synchronize Spyder's path list with PYTHONPATH environment variable
        Only apply to: current user, on Windows platforms
        """
        answer = QMessageBox.question(self, _("Synchronize"),
            _("This will synchronize Spyder's path list with "
                    "<b>PYTHONPATH</b> environment variable for current user, "
                    "allowing you to run your Python modules outside Spyder "
                    "without having to configure sys.path. "
                    "<br>Do you want to clear contents of PYTHONPATH before "
                    "adding Spyder's path list?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if answer == QMessageBox.Cancel:
            return
        elif answer == QMessageBox.Yes:
            remove = True
        else:
            remove = False
        from spyder.utils.environ import (get_user_env, set_user_env,
                                          listdict2envdict)
        env = get_user_env()
        if remove:
            ppath = self.active_pathlist+self.ro_pathlist
        else:
            ppath = env.get('PYTHONPATH', [])
            if not isinstance(ppath, list):
                ppath = [ppath]
            ppath = [path for path in ppath
                     if path not in (self.active_pathlist+self.ro_pathlist)]
            ppath.extend(self.active_pathlist+self.ro_pathlist)
        env['PYTHONPATH'] = ppath
        set_user_env(listdict2envdict(env), parent=self)

    def get_path_list(self):
        """Return path list (does not include the read-only path list)"""
        return self.pathlist

    def update_not_active_pathlist(self, item):
        path = item.text()
        if bool(item.checkState()) is True:
            self.remove_from_not_active_pathlist(path)
        else:
            self.add_to_not_active_pathlist(path)

    def add_to_not_active_pathlist(self, path):
        if path not in self.not_active_pathlist:
            self.not_active_pathlist.append(path)

    def remove_from_not_active_pathlist(self, path):
        if path in self.not_active_pathlist:
            self.not_active_pathlist.remove(path)
        
    def update_list(self):
        """Update path list"""
        self.listwidget.clear()
        for name in self.pathlist+self.ro_pathlist:
            item = QListWidgetItem(name)
            item.setIcon(ima.icon('DirClosedIcon'))
            if name in self.ro_pathlist:
                item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            elif name in self.not_active_pathlist:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Unchecked)
            else:
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
            self.listwidget.addItem(item)
        self.refresh()
        
    def refresh(self, row=None):
        """Refresh widget"""
        for widget in self.selection_widgets:
            widget.setEnabled(self.listwidget.currentItem() is not None)
        not_empty = self.listwidget.count() > 0
        if self.sync_button is not None:
            self.sync_button.setEnabled(not_empty)
    
    def move_to(self, absolute=None, relative=None):
        index = self.listwidget.currentRow()
        if absolute is not None:
            if absolute:
                new_index = len(self.pathlist)-1
            else:
                new_index = 0
        else:
            new_index = index + relative        
        new_index = max(0, min(len(self.pathlist)-1, new_index))
        path = self.pathlist.pop(index)
        self.pathlist.insert(new_index, path)
        self.update_list()
        self.listwidget.setCurrentRow(new_index)

    @Slot()
    def remove_path(self):
        answer = QMessageBox.warning(self, _("Remove path"),
            _("Do you really want to remove selected path?"),
            QMessageBox.Yes | QMessageBox.No)
        if answer == QMessageBox.Yes:
            self.pathlist.pop(self.listwidget.currentRow())
            self.remove_from_not_active_pathlist(
                    self.listwidget.currentItem().text())
            self.update_list()

    @Slot()
    def add_path(self):
        self.redirect_stdio.emit(False)
        directory = getexistingdirectory(self, _("Select directory"),
                                         self.last_path)
        self.redirect_stdio.emit(True)
        if directory:
            directory = osp.abspath(directory)
            self.last_path = directory
            if directory in self.pathlist:
                item = self.listwidget.findItems(directory, Qt.MatchExactly)[0]
                item.setCheckState(Qt.Checked)
                answer = QMessageBox.question(self, _("Add path"),
                    _("This directory is already included in Spyder path "
                            "list.<br>Do you want to move it to the top of "
                            "the list?"),
                    QMessageBox.Yes | QMessageBox.No)
                if answer == QMessageBox.Yes:
                    self.pathlist.remove(directory)
                else:
                    return
            self.pathlist.insert(0, directory)
            self.update_list()
コード例 #22
0
class LoadDataWidget(FormBaseWidget):

    update_main_window_title = Signal()
    update_global_state = Signal()
    computations_complete = Signal(object)

    update_preview_map_range = Signal(str)
    signal_new_run_loaded = Signal(bool)  # True/False - success/failed
    signal_loading_new_run = Signal()  # Emitted before new run is loaded

    signal_data_channel_changed = Signal(bool)

    def __init__(self, *, gpc, gui_vars):
        super().__init__()

        # Global processing classes
        self.gpc = gpc
        # Global GUI variables (used for control of GUI state)
        self.gui_vars = gui_vars

        self.ref_main_window = self.gui_vars["ref_main_window"]

        self.update_global_state.connect(
            self.ref_main_window.update_widget_state)

        self.initialize()

    def initialize(self):

        v_spacing = global_gui_parameters["vertical_spacing_in_tabs"]

        vbox = QVBoxLayout()

        self._setup_wd_group()
        vbox.addWidget(self.group_wd)
        vbox.addSpacing(v_spacing)

        self._setup_load_group()
        vbox.addWidget(self.group_file)
        vbox.addSpacing(v_spacing)

        self._setup_sel_channel_group()
        vbox.addWidget(self.group_sel_channel)
        vbox.addSpacing(v_spacing)

        self._setup_spec_settings_group()
        vbox.addWidget(self.group_spec_settings)
        vbox.addSpacing(v_spacing)

        self._setup_preview_group()
        vbox.addWidget(self.group_preview)

        vbox.addStretch(1)

        self.setLayout(vbox)

        self._set_tooltips()

    def _setup_wd_group(self):
        self.group_wd = QGroupBox("Working Directory")

        self.pb_set_wd = PushButtonMinimumWidth("..")
        self.pb_set_wd.clicked.connect(self.pb_set_wd_clicked)

        self.le_wd = LineEditReadOnly()

        # Initial working directory. Set to the HOME directory for now
        current_dir = os.path.expanduser(
            self.gpc.get_current_working_directory())
        self.le_wd.setText(current_dir)

        hbox = QHBoxLayout()
        hbox.addWidget(self.pb_set_wd)
        hbox.addWidget(self.le_wd)

        self.group_wd.setLayout(hbox)

    def _setup_load_group(self):

        self.group_file = QGroupBox("Load Data")

        self.pb_file = QPushButton("Read File ...")
        self.pb_file.clicked.connect(self.pb_file_clicked)

        self.pb_dbase = QPushButton("Load Run ...")
        self.pb_dbase.setEnabled(
            self.gui_vars["gui_state"]["databroker_available"])
        self.pb_dbase.clicked.connect(self.pb_dbase_clicked)

        self.cb_file_all_channels = QCheckBox("All channels")
        self.cb_file_all_channels.setChecked(self.gpc.get_load_each_channel())
        self.cb_file_all_channels.toggled.connect(
            self.cb_file_all_channels_toggled)

        self.le_file_default = "No data is loaded"
        self.le_file = LineEditReadOnly(self.le_file_default)

        self.pb_view_metadata = QPushButton("View Metadata ...")
        self.pb_view_metadata.setEnabled(False)
        self.pb_view_metadata.clicked.connect(self.pb_view_metadata_clicked)

        vbox = QVBoxLayout()

        hbox = QHBoxLayout()
        hbox.addWidget(self.pb_file)
        hbox.addWidget(self.pb_dbase)
        hbox.addWidget(self.cb_file_all_channels)
        vbox.addLayout(hbox)

        vbox.addWidget(self.le_file)

        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(self.pb_view_metadata)
        vbox.addLayout(hbox)

        self.group_file.setLayout(vbox)

    def _setup_sel_channel_group(self):

        self.group_sel_channel = QGroupBox("Select Channel For Processing")

        self.cbox_channel = QComboBox()
        self.cbox_channel.currentIndexChanged.connect(
            self.cbox_channel_index_changed)
        self._set_cbox_channel_items(items=[])

        hbox = QHBoxLayout()
        hbox.addWidget(self.cbox_channel)

        self.group_sel_channel.setLayout(hbox)

    def _setup_spec_settings_group(self):

        self.group_spec_settings = QGroupBox("Total Spectrum Settings")

        self.pb_apply_mask = QPushButton("Apply Mask ...")
        self.pb_apply_mask.clicked.connect(self.pb_apply_mask_clicked)

        hbox = QHBoxLayout()
        hbox.addWidget(self.pb_apply_mask)
        hbox.addStretch(1)

        self.group_spec_settings.setLayout(hbox)

    def _setup_preview_group(self):

        self.group_preview = QGroupBox("Preview")

        self.list_preview = QListWidget()
        self.list_preview.itemChanged.connect(self.list_preview_item_changed)
        self.list_preview.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.list_preview.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self._set_list_preview_items(items=[])

        hbox = QHBoxLayout()
        hbox.addWidget(self.list_preview)

        self.group_preview.setLayout(hbox)

    def _set_tooltips(self):
        set_tooltip(
            self.pb_set_wd,
            "Select <b>Working Directory</b>. The Working Directory is "
            "used as <b>default</b> for loading and saving data and "
            "configuration files.",
        )
        set_tooltip(self.le_wd, "Currently selected <b>Working Directory</b>")
        set_tooltip(self.pb_file, "Load data from a <b>file on disk</b>.")
        set_tooltip(self.pb_dbase,
                    "Load data from a <b>database</b> (Databroker).")
        set_tooltip(
            self.cb_file_all_channels,
            "Load <b>all</b> available data channels (checked) or only the <b>sum</b> of the channels",
        )
        set_tooltip(
            self.le_file,
            "The <b>name</b> of the loaded file or <b>ID</b> of the loaded run."
        )
        set_tooltip(self.pb_view_metadata,
                    "View scan <b>metadata</b> (if available)")
        set_tooltip(
            self.cbox_channel,
            "Select channel for processing. Typically the <b>sum</b> channel is used."
        )
        set_tooltip(
            self.pb_apply_mask,
            "Load the mask from file and/or select spatial ROI. The mask and ROI "
            "are used in run <b>Preview</b> and fitting of the <b>total spectrum</b>.",
        )
        set_tooltip(
            self.list_preview,
            "Data for the selected channels is displayed in <b>Preview</b> tab. "
            "The displayed <b>total spectrum</b> is computed for the selected "
            "ROI and using the loaded mask. If no mask or ROI are enabled, then "
            "total spectrum is computed over all pixels of the image.",
        )

    def update_widget_state(self, condition=None):
        if condition == "tooltips":
            self._set_tooltips()

        state = self.gui_vars["gui_state"]["state_file_loaded"]
        self.group_sel_channel.setEnabled(state)
        self.group_spec_settings.setEnabled(state)
        self.group_preview.setEnabled(state)

    def _set_cbox_channel_items(self, *, items=None):
        """
        Set items of the combo box for item selection. If the list of items is not specified,
        then it is loaded from the respective data structure.

        Parameters
        ----------
        items: list(str)
            The list of items. The list may be cleared by calling
            `self._set_cbox_channel_items(items=[])`

        """
        self.cbox_channel.currentIndexChanged.disconnect(
            self.cbox_channel_index_changed)

        self.cbox_channel.clear()
        if items is None:
            items = list(self.gpc.get_file_channel_list())
        self.cbox_channel.addItems(items)

        self.cbox_channel.currentIndexChanged.connect(
            self.cbox_channel_index_changed)

        if len(items):
            # Select the first item (if there is at least one item)
            self.cbox_channel.setCurrentIndex(0)

    def _set_list_preview_items(self, *, items=None):
        """
        Set items of the list for selecting channels in preview tab. If the list of items is
        not specified, then it is loaded from the respective data structure.

        Parameters
        ----------
        items: list(str)
            The list of items. The list may be cleared by calling
            `self._set_cbox_channel_items(items=[])`

        """
        self.list_preview.itemChanged.disconnect(
            self.list_preview_item_changed)

        self.list_preview.clear()
        if items is None:
            items = list(self.gpc.get_file_channel_list())
        for s in items:
            wi = QListWidgetItem(s, self.list_preview)
            wi.setFlags(wi.flags() | Qt.ItemIsUserCheckable)
            wi.setFlags(wi.flags() & ~Qt.ItemIsSelectable)
            wi.setCheckState(Qt.Unchecked)

        # Adjust height so that it fits all the elements
        adjust_qlistwidget_height(self.list_preview,
                                  other_widgets=[self.group_preview, self])

        # This will cause the preview data to be plotted (the plot is expected to be hidden,
        #   since no channels were selected). Here we select the first channel in the list.
        for n, item in enumerate(items):
            state = Qt.Checked if self.gpc.is_dset_item_selected_for_preview(
                item) else Qt.Unchecked
            self.list_preview.item(n).setCheckState(state)

        self.list_preview.itemChanged.connect(self.list_preview_item_changed)

    def pb_set_wd_clicked(self):
        dir_current = self.le_wd.text()
        dir = QFileDialog.getExistingDirectory(
            self,
            "Select Working Directory",
            dir_current,
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks,
        )
        if dir:
            self.gpc.set_current_working_directory(dir)
            self.le_wd.setText(dir)

    def pb_file_clicked(self):
        dir_current = self.gpc.get_current_working_directory()
        file_paths = QFileDialog.getOpenFileName(self, "Open Data File",
                                                 dir_current,
                                                 "HDF5 (*.h5);; All (*)")
        file_path = file_paths[0]
        if file_path:
            self.signal_loading_new_run.emit()

            def cb(file_path):
                result_dict = {}
                try:
                    msg = self.gpc.open_data_file(file_path)
                    status = True
                except Exception as ex:
                    msg = str(ex)
                    status = False
                result_dict.update({
                    "status": status,
                    "msg": msg,
                    "file_path": file_path
                })
                return result_dict

            self._compute_in_background(cb,
                                        self.slot_file_clicked,
                                        file_path=file_path)

    @Slot(object)
    def slot_file_clicked(self, result):
        self._recover_after_compute(self.slot_file_clicked)

        status = result["status"]
        msg = result["msg"]  # Message is empty if file loading failed
        file_path = result["file_path"]
        if status:
            file_text = f"'{self.gpc.get_loaded_file_name()}'"
            if self.gpc.is_scan_metadata_available():
                file_text += f": ID#{self.gpc.get_metadata_scan_id()}"
            self.le_file.setText(file_text)

            self.gui_vars["gui_state"]["state_file_loaded"] = True
            # Invalidate fit. Fit must be rerun for new data.
            self.gui_vars["gui_state"]["state_model_fit_exists"] = False
            # Check if any datasets were loaded.
            self.gui_vars["gui_state"][
                "state_xrf_map_exists"] = self.gpc.is_xrf_maps_available()

            # Disable the button for changing working directory. This is consistent
            #   with the behavior of the old PyXRF, but will be changed in the future.
            self.pb_set_wd.setEnabled(False)

            # Enable/disable 'View Metadata' button
            self.pb_view_metadata.setEnabled(
                self.gpc.is_scan_metadata_available())
            self.le_wd.setText(self.gpc.get_current_working_directory())

            self.update_main_window_title.emit()
            self.update_global_state.emit()

            self._set_cbox_channel_items()
            self._set_list_preview_items()

            # Here we want to reset the range in the Total Count Map preview
            self.update_preview_map_range.emit("reset")

            self.signal_new_run_loaded.emit(
                True)  # Data is loaded successfully

            if msg:
                # Display warning message if it was generated
                msgbox = QMessageBox(QMessageBox.Warning,
                                     "Warning",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()

        else:
            self.le_file.setText(self.le_file_default)

            # Disable 'View Metadata' button
            self.pb_view_metadata.setEnabled(False)
            self.le_wd.setText(self.gpc.get_current_working_directory())

            # Clear flags: the state now is "No data is loaded".
            clear_gui_state(self.gui_vars)
            self.update_global_state.emit()

            self.update_main_window_title.emit()
            self.update_global_state.emit()

            self._set_cbox_channel_items(items=[])
            self._set_list_preview_items(items=[])

            # Here we want to clear the range in the Total Count Map preview
            self.update_preview_map_range.emit("clear")

            self.signal_new_run_loaded.emit(False)  # Failed to load data

            msg_str = (f"Incorrect format of input file '{file_path}': "
                       f"PyXRF accepts only custom HDF (.h5) files."
                       f"\n\nError message: {msg}")
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg_str,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()

    def pb_dbase_clicked(self):

        dlg = DialogSelectScan()
        if dlg.exec() == QDialog.Accepted:
            mode, id_uid = dlg.get_id_uid()

            self.signal_loading_new_run.emit()

            def cb(id_uid):
                result_dict = {}
                try:
                    msg, file_name = self.gpc.load_run_from_db(id_uid)
                    status = True
                except Exception as ex:
                    msg, file_name = str(ex), ""
                    status = False
                result_dict.update({
                    "status": status,
                    "msg": msg,
                    "id_uid": id_uid,
                    "file_name": file_name
                })
                return result_dict

            self._compute_in_background(cb,
                                        self.slot_dbase_clicked,
                                        id_uid=id_uid)

    @Slot(object)
    def slot_dbase_clicked(self, result):
        self._recover_after_compute(self.slot_dbase_clicked)

        status = result["status"]
        msg = result["msg"]  # Message is empty if file loading failed
        id_uid = result["id_uid"]
        # file_name = result["file_name"]
        if status:
            file_text = f"'{self.gpc.get_loaded_file_name()}'"
            if self.gpc.is_scan_metadata_available():
                file_text += f": ID#{self.gpc.get_metadata_scan_id()}"
            self.le_file.setText(file_text)

            self.gui_vars["gui_state"]["state_file_loaded"] = True
            # Invalidate fit. Fit must be rerun for new data.
            self.gui_vars["gui_state"]["state_model_fit_exists"] = False
            # Check if any datasets were loaded.
            self.gui_vars["gui_state"][
                "state_xrf_map_exists"] = self.gpc.is_xrf_maps_available()

            # Disable the button for changing working directory. This is consistent
            #   with the behavior of the old PyXRF, but will be changed in the future.
            self.pb_set_wd.setEnabled(False)

            # Enable/disable 'View Metadata' button
            self.pb_view_metadata.setEnabled(
                self.gpc.is_scan_metadata_available())
            self.le_wd.setText(self.gpc.get_current_working_directory())

            self.update_main_window_title.emit()
            self.update_global_state.emit()

            self._set_cbox_channel_items()
            self._set_list_preview_items()

            # Here we want to reset the range in the Total Count Map preview
            self.update_preview_map_range.emit("reset")

            self.signal_new_run_loaded.emit(
                True)  # Data is loaded successfully

            if msg:
                # Display warning message if it was generated
                msgbox = QMessageBox(QMessageBox.Warning,
                                     "Warning",
                                     msg,
                                     QMessageBox.Ok,
                                     parent=self)
                msgbox.exec()

        else:
            self.le_file.setText(self.le_file_default)

            # Disable 'View Metadata' button
            self.pb_view_metadata.setEnabled(False)
            self.le_wd.setText(self.gpc.get_current_working_directory())

            # Clear flags: the state now is "No data is loaded".
            clear_gui_state(self.gui_vars)
            self.update_global_state.emit()

            self.update_main_window_title.emit()
            self.update_global_state.emit()

            self._set_cbox_channel_items(items=[])
            self._set_list_preview_items(items=[])

            # Here we want to clear the range in the Total Count Map preview
            self.update_preview_map_range.emit("clear")

            self.signal_new_run_loaded.emit(False)  # Failed to load data

            msg_str = f"Failed to load scan '{id_uid}'.\n\nError message: {msg}"
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg_str,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()

    def pb_apply_mask_clicked(self):
        map_size = self.gpc.get_dataset_map_size()
        map_size = map_size if (map_size is not None) else (0, 0)

        dlg = DialogLoadMask()
        dlg.set_image_size(n_rows=map_size[0], n_columns=map_size[1])
        roi = self.gpc.get_preview_spatial_roi()
        dlg.set_roi(
            row_start=roi["row_start"],
            column_start=roi["col_start"],
            row_end=roi["row_end"],
            column_end=roi["col_end"],
        )
        dlg.set_roi_active(self.gpc.is_roi_selection_active())

        dlg.set_default_directory(self.gpc.get_current_working_directory())
        dlg.set_mask_file_path(self.gpc.get_mask_selection_file_path())
        dlg.set_mask_file_active(self.gpc.is_mask_selection_active())

        if dlg.exec() == QDialog.Accepted:
            roi_keys = ("row_start", "col_start", "row_end", "col_end")
            roi_list = dlg.get_roi()
            roi_selected = {k: roi_list[n] for n, k in enumerate(roi_keys)}
            self.gpc.set_preview_spatial_roi(roi_selected)

            self.gpc.set_roi_selection_active(dlg.get_roi_active())

            self.gpc.set_mask_selection_file_path(dlg.get_mask_file_path())
            self.gpc.set_mask_selection_active(dlg.get_mask_file_active())

            def cb():
                try:
                    # TODO: proper error processing is needed here (exception RuntimeError)
                    self.gpc.apply_mask_to_datasets()
                    success = True
                    msg = ""
                except Exception as ex:
                    success = False
                    msg = str(ex)
                return {"success": success, "msg": msg}

            self._compute_in_background(cb, self.slot_apply_mask_clicked)

    @Slot(object)
    def slot_apply_mask_clicked(self, result):
        if not result["success"]:
            msg = f"Error occurred while applying the ROI selection:\nException: {result['msg']}"
            logger.error(f"{msg}")
            mb_error = QMessageBox(QMessageBox.Critical,
                                   "Error",
                                   f"{msg}",
                                   QMessageBox.Ok,
                                   parent=self)
            mb_error.exec()

        # Here we want to expand the range in the Total Count Map preview if needed
        self.update_preview_map_range.emit("update")
        self._recover_after_compute(self.slot_apply_mask_clicked)

    def pb_view_metadata_clicked(self):

        dlg = DialogViewMetadata()
        metadata_string = self.gpc.get_formatted_metadata()
        dlg.setText(metadata_string)
        dlg.exec()

    def cb_file_all_channels_toggled(self, state):
        self.gpc.set_load_each_channel(state)

    def cbox_channel_index_changed(self, index):
        def cb(index):
            try:
                self.gpc.set_data_channel(index)
                success, msg = True, ""
            except Exception as ex:
                success = False
                msg = str(ex)
            return {"success": success, "msg": msg}

        self._compute_in_background(cb,
                                    self.slot_channel_index_changed,
                                    index=index)

    @Slot(object)
    def slot_channel_index_changed(self, result):
        self._recover_after_compute(self.slot_channel_index_changed)

        if result["success"]:
            self.signal_data_channel_changed.emit(True)
        else:
            self.signal_data_channel_changed.emit(False)
            msg = result["msg"]
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()

    def list_preview_item_changed(self, list_item):
        # Find the index of the list item that was checked/unchecked
        ind = -1
        for n in range(self.list_preview.count()):
            if self.list_preview.item(n) == list_item:
                ind = n

        # Get the state of the checkbox (checked -> 2, unchecked -> 0)
        state = list_item.checkState()

        # The name of the dataset
        dset_name = self.gpc.get_file_channel_list()[ind]

        def cb():
            try:
                self.gpc.select_preview_dataset(dset_name=dset_name,
                                                is_visible=bool(state))
                success, msg = True, ""
            except Exception as ex:
                success = False
                msg = str(ex)
            return {"success": success, "msg": msg}

        self._compute_in_background(cb, self.slot_preview_items_changed)

    @Slot(object)
    def slot_preview_items_changed(self, result):
        if not result["success"]:
            # The error shouldn't actually happen here. This is to prevent potential crashes.
            msg = f"Error occurred: {result['msg']}.\nData may need to be reloaded to continue processing."
            msgbox = QMessageBox(QMessageBox.Critical,
                                 "Error",
                                 msg,
                                 QMessageBox.Ok,
                                 parent=self)
            msgbox.exec()

        # Here we want to expand the range in the Total Count Map preview if needed
        self.update_preview_map_range.emit("expand")
        self._recover_after_compute(self.slot_preview_items_changed)

    def _compute_in_background(self, func, slot, *args, **kwargs):
        """
        Run function `func` in a background thread. Send the signal
        `self.computations_complete` once computation is finished.

        Parameters
        ----------
        func: function
            Reference to a function that is supposed to be executed at the background.
            The function return value is passed as a signal parameter once computation is
            complete.
        slot: qtpy.QtCore.Slot or None
            Reference to a slot. If not None, then the signal `self.computation_complete`
            is connected to this slot.
        args, kwargs
            arguments of the function `func`.
        """
        signal_complete = self.computations_complete

        def func_to_run(func, *args, **kwargs):
            class LoadFile(QRunnable):
                def run(self):
                    result_dict = func(*args, **kwargs)
                    signal_complete.emit(result_dict)

            return LoadFile()

        if slot is not None:
            self.computations_complete.connect(slot)
        self.gui_vars["gui_state"]["running_computations"] = True
        self.update_global_state.emit()
        QThreadPool.globalInstance().start(func_to_run(func, *args, **kwargs))

    def _recover_after_compute(self, slot):
        """
        The function should be called after the signal `self.computations_complete` is
        received. The slot should be the same as the one used when calling
        `self.compute_in_background`.
        """
        if slot is not None:
            self.computations_complete.disconnect(slot)
        self.gui_vars["gui_state"]["running_computations"] = False
        self.update_global_state.emit()
コード例 #23
0
ファイル: checklist.py プロジェクト: oyvindeide/ert
class CheckList(QWidget):
    def __init__(self, model, label="", help_link="", custom_filter_button=None):
        """
        :param custom_filter_button:  if needed, add a button that opens a
        custom filter menu. Useful when search alone isn't enough to filter the
        list.
        :type custom_filter_button: QToolButton
        """
        QWidget.__init__(self)

        self._model = model

        if help_link != "":
            addHelpToWidget(self, help_link)

        layout = QVBoxLayout()

        self._createCheckButtons()

        self._list = QListWidget()
        self._list.setContextMenuPolicy(Qt.CustomContextMenu)
        self._list.setSelectionMode(QAbstractItemView.ExtendedSelection)

        self._search_box = SearchBox()

        check_button_layout = QHBoxLayout()

        check_button_layout.setContentsMargins(0, 0, 0, 0)
        check_button_layout.setSpacing(0)
        check_button_layout.addWidget(QLabel(label))
        check_button_layout.addStretch(1)
        check_button_layout.addWidget(self._checkAllButton)
        check_button_layout.addWidget(self._uncheckAllButton)

        layout.addLayout(check_button_layout)
        layout.addWidget(self._list)

        """
        Inserts the custom filter button, if provided. The caller is responsible
        for all related actions.
        """
        if custom_filter_button is not None:
            search_bar_layout = QHBoxLayout()
            search_bar_layout.addWidget(self._search_box)
            search_bar_layout.addWidget(custom_filter_button)
            layout.addLayout(search_bar_layout)
        else:
            layout.addWidget(self._search_box)

        self.setLayout(layout)

        self._checkAllButton.clicked.connect(self.checkAll)
        self._uncheckAllButton.clicked.connect(self.uncheckAll)
        self._list.itemChanged.connect(self.itemChanged)
        self._search_box.filterChanged.connect(self.filterList)
        self._list.customContextMenuRequested.connect(self.showContextMenu)

        self._model.selectionChanged.connect(self.modelChanged)
        self._model.modelChanged.connect(self.modelChanged)

        self.modelChanged()

    def _createCheckButtons(self):
        self._checkAllButton = QToolButton()
        self._checkAllButton.setIcon(resourceIcon("check.svg"))
        self._checkAllButton.setIconSize(QSize(16, 16))
        self._checkAllButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self._checkAllButton.setAutoRaise(True)
        self._checkAllButton.setToolTip("Select all")
        self._uncheckAllButton = QToolButton()
        self._uncheckAllButton.setIcon(resourceIcon("checkbox_outline.svg"))
        self._uncheckAllButton.setIconSize(QSize(16, 16))
        self._uncheckAllButton.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self._uncheckAllButton.setAutoRaise(True)
        self._uncheckAllButton.setToolTip("Unselect all")

    def itemChanged(self, item):
        """@type item: QListWidgetItem"""
        if item.checkState() == Qt.Checked:
            self._model.selectValue(str(item.text()))
        elif item.checkState() == Qt.Unchecked:
            self._model.unselectValue(str(item.text()))
        else:
            raise AssertionError("Unhandled checkstate!")

    def modelChanged(self):
        self._list.clear()

        items = self._model.getList()

        for item in items:
            list_item = QListWidgetItem(item)
            list_item.setFlags(list_item.flags() | Qt.ItemIsUserCheckable)

            if self._model.isValueSelected(item):
                list_item.setCheckState(Qt.Checked)
            else:
                list_item.setCheckState(Qt.Unchecked)

            self._list.addItem(list_item)

        self.filterList(self._search_box.filter())

    def setSelectionEnabled(self, enabled):
        self.setEnabled(enabled)
        self._checkAllButton.setEnabled(enabled)
        self._uncheckAllButton.setEnabled(enabled)

    def filterList(self, filter):
        filter = filter.lower()

        for index in range(0, self._list.count()):
            item = self._list.item(index)
            text = str(item.text()).lower()

            if filter == "":
                item.setHidden(False)
            elif filter in text:
                item.setHidden(False)
            else:
                item.setHidden(True)

    def checkAll(self):
        """
        Checks all visible items in the list.
        """
        for index in range(0, self._list.count()):
            item = self._list.item(index)
            if not item.isHidden():
                self._model.selectValue(str(item.text()))

    def uncheckAll(self):
        """
        Unchecks all items in the list, visible or not
        """
        self._model.unselectAll()

    def checkSelected(self):
        items = []
        for item in self._list.selectedItems():
            items.append(str(item.text()))

        for item in items:
            self._model.selectValue(item)

    def uncheckSelected(self):
        items = []
        for item in self._list.selectedItems():
            items.append(str(item.text()))

        for item in items:
            self._model.unselectValue(item)

    def showContextMenu(self, point):
        p = self._list.mapToGlobal(point)
        menu = QMenu()
        check_selected = menu.addAction("Check selected")
        uncheck_selected = menu.addAction("Uncheck selected")
        menu.addSeparator()
        clear_selection = menu.addAction("Clear selection")

        selected_item = menu.exec_(p)

        if selected_item == check_selected:
            self.checkSelected()
        elif selected_item == uncheck_selected:
            self.uncheckSelected()
        elif selected_item == clear_selection:
            self._list.clearSelection()
コード例 #24
0
ファイル: preferences_dialog.py プロジェクト: Zac-HD/napari
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    resized = Signal(QSize)

    def __init__(self, parent=None):
        super().__init__(parent)

        self._list = QListWidget(self)
        self._stack = QStackedWidget(self)

        self._list.setObjectName("Preferences")

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_ok = QPushButton(trans._("OK"))
        self._default_restore = QPushButton(trans._("Restore defaults"))

        # Setup
        self.setWindowTitle(trans._("Preferences"))

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._default_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        main_layout = QHBoxLayout()
        main_layout.addLayout(left_layout, 1)
        main_layout.addWidget(self._stack, 3)

        self.setLayout(main_layout)

        # Signals

        self._list.currentRowChanged.connect(
            lambda index: self._stack.setCurrentIndex(index))
        self._button_cancel.clicked.connect(self.on_click_cancel)
        self._button_ok.clicked.connect(self.on_click_ok)
        self._default_restore.clicked.connect(self.restore_defaults)

        # Make widget

        self.make_dialog()
        self._list.setCurrentRow(0)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def make_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # Because there are multiple pages, need to keep a list of values sets.
        self._values_orig_set_list = []
        self._values_set_list = []
        for _key, setting in SETTINGS.schemas().items():
            schema = json.loads(setting['json_schema'])
            # Need to remove certain properties that will not be displayed on the GUI
            properties = schema.pop('properties')
            model = setting['model']
            values = model.dict()
            napari_config = getattr(model, "NapariConfig", None)
            if napari_config is not None:
                for val in napari_config.preferences_exclude:
                    properties.pop(val)
                    values.pop(val)

            schema['properties'] = properties
            self._values_orig_set_list.append(set(values.items()))
            self._values_set_list.append(set(values.items()))

            # Only add pages if there are any properties to add.
            if properties:
                self.add_page(schema, values)

    def restore_defaults(self):
        """Launches dialog to confirm restore settings choice."""

        widget = ConfirmDialog(
            parent=self,
            text=trans._("Are you sure you want to restore default settings?"),
        )
        widget.valueChanged.connect(self._reset_widgets)
        widget.exec_()

    def _reset_widgets(self):
        """Deletes the widgets and rebuilds with defaults."""
        self.close()
        self._list.clear()

        for n in range(self._stack.count()):
            widget = self._stack.removeWidget(self._stack.currentWidget())
            del widget

        self.make_dialog()
        self._list.setCurrentRow(0)
        self.show()

    def on_click_ok(self):
        """Keeps the selected preferences saved to SETTINGS."""
        self.close()

    def on_click_cancel(self):
        """Restores the settings in place when dialog was launched."""
        # Need to check differences for each page.
        for n in range(self._stack.count()):
            # Must set the current row so that the proper set list is updated
            # in check differences.
            self._list.setCurrentRow(n)
            self.check_differences(
                self._values_orig_set_list[n],
                self._values_set_list[n],
            )
        self._list.setCurrentRow(0)
        self.close()

    def add_page(self, schema, values):
        """Creates a new page for each section in dialog.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """
        widget = self.build_page_dialog(schema, values)
        self._list.addItem(schema["title"])
        self._stack.addWidget(widget)

    def build_page_dialog(self, schema, values):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        schema : dict
            Json schema including all information to build each page in the
            preferences dialog.
        values : dict
            Dictionary of current values set in preferences.
        """

        builder = WidgetBuilder()
        form = builder.create_form(schema, {})
        # set state values for widget
        form.widget.state = values
        form.widget.on_changed.connect(lambda d: self.check_differences(
            set(d.items()),
            self._values_set_list[self._list.currentIndex().row()],
        ))

        return form

    def check_differences(self, new_set, values_set):
        """Changes settings in settings manager with changes from dialog.

        Parameters
        ----------
        new_set : set
            The set of new values, with tuples of key value pairs for each
            setting.
        values_set : set
            The old set of values.
        """

        page = self._list.currentItem().text().split(" ")[0].lower()
        different_values = list(new_set - values_set)

        if len(different_values) > 0:
            # change the values in SETTINGS
            for val in different_values:
                try:
                    setattr(SETTINGS._settings[page], val[0], val[1])
                    self._values_set_list[
                        self._list.currentIndex().row()] = new_set
                except:  # noqa: E722
                    continue
コード例 #25
0
ファイル: fileassociations.py プロジェクト: nixjdm/spyder
class ApplicationsDialog(QDialog):
    """Dialog for selection of installed system/user applications."""
    def __init__(self, parent=None):
        """Dialog for selection of installed system/user applications."""
        super(ApplicationsDialog, self).__init__(parent=parent)

        # Widgets
        self.label = QLabel()
        self.label_browse = QLabel()
        self.edit_filter = QLineEdit()
        self.list = QListWidget()
        self.button_browse = QPushButton(_('Browse...'))
        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok
                                           | QDialogButtonBox.Cancel)
        self.button_ok = self.button_box.button(QDialogButtonBox.Ok)
        self.button_cancel = self.button_box.button(QDialogButtonBox.Cancel)

        # Widget setup
        self.setWindowTitle(_('Applications'))
        self.edit_filter.setPlaceholderText(_('Type to filter by name'))
        self.list.setIconSize(QSize(16, 16))  # FIXME: Use metrics

        # Layout
        layout = QVBoxLayout()
        layout.addWidget(self.label)
        layout.addWidget(self.edit_filter)
        layout.addWidget(self.list)
        layout_browse = QHBoxLayout()
        layout_browse.addWidget(self.button_browse)
        layout_browse.addWidget(self.label_browse)
        layout.addLayout(layout_browse)
        layout.addSpacing(12)  # FIXME: Use metrics
        layout.addWidget(self.button_box)
        self.setLayout(layout)

        # Signals
        self.edit_filter.textChanged.connect(self.filter)
        self.button_browse.clicked.connect(lambda x: self.browse())
        self.button_ok.clicked.connect(self.accept)
        self.button_cancel.clicked.connect(self.reject)
        self.list.currentItemChanged.connect(self._refresh)

        self._refresh()
        self.setup()

    def setup(self, applications=None):
        """Load installed applications."""
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        self.list.clear()
        if applications is None:
            apps = get_installed_applications()
        else:
            apps = applications

        for app in sorted(apps, key=lambda x: x.lower()):
            fpath = apps[app]
            icon = get_application_icon(fpath)
            item = QListWidgetItem(icon, app)
            item.setToolTip(fpath)
            item.fpath = fpath
            self.list.addItem(item)

        # FIXME: Use metrics
        self.list.setMinimumWidth(self.list.sizeHintForColumn(0) + 24)
        QApplication.restoreOverrideCursor()
        self._refresh()

    def _refresh(self):
        """Refresh the status of buttons on widget."""
        self.button_ok.setEnabled(self.list.currentRow() != -1)

    def browse(self, fpath=None):
        """Prompt user to select an application not found on the list."""
        app = None
        item = None

        if sys.platform == 'darwin':
            if fpath is None:
                basedir = '/Applications/'
                filters = _('Applications (*.app)')
                title = _('Select application')
                fpath, __ = getopenfilename(self, title, basedir, filters)

            if fpath and fpath.endswith('.app') and os.path.isdir(fpath):
                app = os.path.basename(fpath).split('.app')[0]
                for row in range(self.list.count()):
                    item = self.list.item(row)
                    if app == item.text() and fpath == item.fpath:
                        break
                else:
                    item = None
        elif os.name == 'nt':
            if fpath is None:
                basedir = 'C:\\'
                filters = _('Applications (*.exe *.bat *.com)')
                title = _('Select application')
                fpath, __ = getopenfilename(self, title, basedir, filters)

            if fpath:
                check_1 = fpath.endswith('.bat') and is_text_file(fpath)
                check_2 = (fpath.endswith(('.exe', '.com'))
                           and not is_text_file(fpath))
                if check_1 or check_2:
                    app = os.path.basename(fpath).capitalize().rsplit('.')[0]
                    for row in range(self.list.count()):
                        item = self.list.item(row)
                        if app == item.text() and fpath == item.fpath:
                            break
                    else:
                        item = None
        else:
            if fpath is None:
                basedir = '/'
                filters = _('Applications (*.desktop)')
                title = _('Select application')
                fpath, __ = getopenfilename(self, title, basedir, filters)

            if fpath and fpath.endswith(('.desktop')) and is_text_file(fpath):
                entry_data = parse_linux_desktop_entry(fpath)
                app = entry_data['name']
                for row in range(self.list.count()):
                    item = self.list.item(row)
                    if app == item.text() and fpath == item.fpath:
                        break
                else:
                    item = None

        if fpath:
            if item:
                self.list.setCurrentItem(item)
            elif app:
                icon = get_application_icon(fpath)
                item = QListWidgetItem(icon, app)
                item.fpath = fpath
                self.list.addItem(item)
                self.list.setCurrentItem(item)

        self.list.setFocus()
        self._refresh()

    def filter(self, text):
        """Filter the list of applications based on text."""
        text = self.edit_filter.text().lower().strip()
        for row in range(self.list.count()):
            item = self.list.item(row)
            item.setHidden(text not in item.text().lower())
        self._refresh()

    def set_extension(self, extension):
        """Set the extension on the label of the dialog."""
        self.label.setText(
            _('Choose the application for files of type ') + extension)

    @property
    def application_path(self):
        """Return the selected application path to executable."""
        item = self.list.currentItem()
        path = item.fpath if item else ''
        return path

    @property
    def application_name(self):
        """Return the selected application name."""
        item = self.list.currentItem()
        text = item.text() if item else ''
        return text
コード例 #26
0
class PreferencesDialog(QDialog):
    """Preferences Dialog for Napari user settings."""

    ui_schema = {
        "call_order": {
            "ui:widget": "plugins"
        },
        "highlight_thickness": {
            "ui:widget": "highlight"
        },
        "shortcuts": {
            "ui:widget": "shortcuts"
        },
        "extension2reader": {
            "ui:widget": "extension2reader"
        },
    }

    resized = Signal(QSize)

    def __init__(self, parent=None):
        from ...settings import get_settings

        super().__init__(parent)
        self.setWindowTitle(trans._("Preferences"))

        self._settings = get_settings()
        self._stack = QStackedWidget(self)
        self._list = QListWidget(self)
        self._list.setObjectName("Preferences")
        self._list.currentRowChanged.connect(self._stack.setCurrentIndex)

        # Set up buttons
        self._button_cancel = QPushButton(trans._("Cancel"))
        self._button_cancel.clicked.connect(self.reject)
        self._button_ok = QPushButton(trans._("OK"))
        self._button_ok.clicked.connect(self.accept)
        self._button_ok.setDefault(True)
        self._button_restore = QPushButton(trans._("Restore defaults"))
        self._button_restore.clicked.connect(self._restore_default_dialog)

        # Layout
        left_layout = QVBoxLayout()
        left_layout.addWidget(self._list)
        left_layout.addStretch()
        left_layout.addWidget(self._button_restore)
        left_layout.addWidget(self._button_cancel)
        left_layout.addWidget(self._button_ok)

        self.setLayout(QHBoxLayout())
        self.layout().addLayout(left_layout, 1)
        self.layout().addWidget(self._stack, 3)

        # Build dialog from settings
        self._rebuild_dialog()

    def keyPressEvent(self, e: 'QKeyEvent'):
        if e.key() == Qt.Key_Escape:
            # escape key should just close the window
            # which implies "accept"
            e.accept()
            self.accept()
            return
        super().keyPressEvent(e)

    def resizeEvent(self, event):
        """Override to emit signal."""
        self.resized.emit(event.size())
        super().resizeEvent(event)

    def _rebuild_dialog(self):
        """Removes settings not to be exposed to user and creates dialog pages."""
        # FIXME: this dialog should not need to know about the plugin manager
        from ...plugins import plugin_manager

        self._starting_pm_order = plugin_manager.call_order()
        self._starting_values = self._settings.dict(exclude={'schema_version'})

        self._list.clear()
        while self._stack.count():
            self._stack.removeWidget(self._stack.currentWidget())

        for field in self._settings.__fields__.values():
            if isinstance(field.type_, type) and issubclass(
                    field.type_, BaseModel):
                self._add_page(field)

        self._list.setCurrentRow(0)

    def _add_page(self, field: 'ModelField'):
        """Builds the preferences widget using the json schema builder.

        Parameters
        ----------
        field : ModelField
            subfield for which to create a page.
        """
        from ..._vendor.qt_json_builder.qt_jsonschema_form import WidgetBuilder

        schema, values = self._get_page_dict(field)
        name = field.field_info.title or field.name

        form = WidgetBuilder().create_form(schema, self.ui_schema)
        # set state values for widget
        form.widget.state = values
        # make settings follow state of the form widget
        form.widget.on_changed.connect(
            lambda d: getattr(self._settings, name.lower()).update(d))

        # need to disable async if octree is enabled.
        # TODO: this shouldn't live here... if there is a coupling/dependency
        # between these settings, it should be declared in the settings schema
        if (name.lower() == 'experimental'
                and values['octree'] and self._settings.env_settings().get(
                    'experimental', {}).get('async_') not in (None, '0')):
            form_layout = form.widget.layout()
            for i in range(form_layout.count()):
                wdg = form_layout.itemAt(i, form_layout.FieldRole).widget()
                if getattr(wdg, '_name') == 'async_':
                    wdg.opacity.setOpacity(0.3)
                    wdg.setDisabled(True)
                    break

        self._list.addItem(field.field_info.title or field.name)
        self._stack.addWidget(form)

    def _get_page_dict(self, field: 'ModelField') -> Tuple[dict, dict, dict]:
        """Provides the schema, set of values for each setting, and the
        properties for each setting."""
        ftype = cast('BaseModel', field.type_)
        schema = json.loads(ftype.schema_json())

        # find enums:
        for name, subfield in ftype.__fields__.items():
            if isinstance(subfield.type_, EnumMeta):
                enums = [s.value for s in subfield.type_]  # type: ignore
                schema["properties"][name]["enum"] = enums
                schema["properties"][name]["type"] = "string"

        # Need to remove certain properties that will not be displayed on the GUI
        setting = getattr(self._settings, field.name)
        with setting.enums_as_values():
            values = setting.dict()
        napari_config = getattr(setting, "NapariConfig", None)
        if hasattr(napari_config, 'preferences_exclude'):
            for val in napari_config.preferences_exclude:
                schema['properties'].pop(val, None)
                values.pop(val, None)

        return schema, values

    def _restore_default_dialog(self):
        """Launches dialog to confirm restore settings choice."""
        response = QMessageBox.question(
            self,
            trans._("Restore Settings"),
            trans._("Are you sure you want to restore default settings?"),
            QMessageBox.RestoreDefaults | QMessageBox.Cancel,
            QMessageBox.RestoreDefaults,
        )
        if response == QMessageBox.RestoreDefaults:
            self._settings.reset()
            self._rebuild_dialog()  # TODO: do we need this?

    def _restart_required_dialog(self):
        """Displays the dialog informing user a restart is required."""
        QMessageBox.information(
            self,
            trans._("Restart required"),
            trans.
            _("A restart is required for some new settings to have an effect."
              ),
        )

    def closeEvent(self, event: 'QCloseEvent') -> None:
        event.accept()
        self.accept()

    def reject(self):
        """Restores the settings in place when dialog was launched."""
        self._settings.update(self._starting_values)

        # FIXME: this dialog should not need to know about the plugin manager
        if self._starting_pm_order:
            from ...plugins import plugin_manager

            plugin_manager.set_call_order(self._starting_pm_order)
        super().reject()
コード例 #27
0
ファイル: fileswitcher.py プロジェクト: wangxiaofei558/spyder
class FileSwitcher(QDialog):
    """A Sublime-like file switcher."""
    sig_goto_file = Signal(int, object)

    # Constants that define the mode in which the list widget is working
    # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols
    # in a given file when using the '@' symbol.
    FILE_MODE, SYMBOL_MODE = [1, 2]
    MAX_WIDTH = 600

    def __init__(self, parent, plugin, tabs, data, icon):
        QDialog.__init__(self, parent)

        # Variables
        self.plugins_tabs = []
        self.plugins_data = []
        self.plugins_instances = []
        self.add_plugin(plugin, tabs, data, icon)
        self.plugin = None  # Last plugin with focus
        self.mode = self.FILE_MODE  # By default start in this mode
        self.initial_cursors = None  # {fullpath: QCursor}
        self.initial_path = None  # Fullpath of initial active editor
        self.initial_widget = None  # Initial active editor
        self.line_number = None  # Selected line number in filer
        self.is_visible = False  # Is the switcher visible?

        help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to "
                      "cancel.<br><br>Type to filter filenames.<br><br>"
                      "Use <b>:number</b> to go to a line, e.g. "
                      "<b><code>main:42</code></b><br>"
                      "Use <b>@symbol_text</b> to go to a symbol, e.g. "
                      "<b><code>@init</code></b>"
                      "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>")

        # Either allow searching for a line number or a symbol but not both
        regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" +
                        "([A-Za-z0-9_]{0,100}:{0,1}[0-9]{0,100})")

        # Widgets
        self.edit = FilesFilterLine(self)
        self.help = HelperToolButton()
        self.list = QListWidget(self)
        self.filter = KeyPressFilter()
        regex_validator = QRegExpValidator(regex, self.edit)

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setValidator(regex_validator)
        self.help.setToolTip(help_text)
        self.list.setItemDelegate(HTMLDelegate(self))

        # Layout
        edit_layout = QHBoxLayout()
        edit_layout.addWidget(self.edit)
        edit_layout.addWidget(self.help)
        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.rejected.connect(self.restore_initial_state)
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.edit.returnPressed.connect(self.accept)
        self.edit.textChanged.connect(self.setup)
        self.list.itemSelectionChanged.connect(self.item_selection_changed)
        self.list.clicked.connect(self.edit.setFocus)

    # --- Properties
    @property
    def widgets(self):
        widgets = []
        for plugin in self.plugins_instances:
            tabs = self.get_plugin_tabwidget(plugin)
            widgets += [(tabs.widget(index), plugin)
                        for index in range(tabs.count())]
        return widgets

    @property
    def line_count(self):
        line_count = []
        for widget in self.widgets:
            try:
                current_line_count = widget[0].get_line_count()
            except AttributeError:
                current_line_count = 0
            line_count.append(current_line_count)
        return line_count

    @property
    def save_status(self):
        save_status = []
        for da, icon in self.plugins_data:
            save_status += [getattr(td, 'newly_created', False) for td in da]
        return save_status

    @property
    def paths(self):
        paths = []
        for plugin in self.plugins_instances:
            da = self.get_plugin_data(plugin)
            paths += [getattr(td, 'filename', None) for td in da]
        return paths

    @property
    def filenames(self):
        filenames = []
        for plugin in self.plugins_instances:
            da = self.get_plugin_data(plugin)
            filenames += [
                os.path.basename(getattr(td, 'filename', None)) for td in da
            ]
        return filenames

    @property
    def icons(self):
        icons = []
        for da, icon in self.plugins_data:
            icons += [icon for td in da]
        return icons

    @property
    def current_path(self):
        return self.paths_by_widget[self.get_widget()]

    @property
    def paths_by_widget(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(widgets, self.paths))

    @property
    def widgets_by_path(self):
        widgets = [w[0] for w in self.widgets]
        return dict(zip(self.paths, widgets))

    @property
    def filter_text(self):
        """Get the normalized (lowecase) content of the filter text."""
        return to_text_string(self.edit.text()).lower()

    def set_search_text(self, _str):
        self.edit.setText(_str)

    def save_initial_state(self):
        """Save initial cursors and initial active widget."""
        paths = self.paths
        self.initial_widget = self.get_widget()
        self.initial_cursors = {}

        for i, editor in enumerate(self.widgets):
            if editor is self.initial_widget:
                self.initial_path = paths[i]
            # This try is needed to make the fileswitcher work with
            # plugins that does not have a textCursor.
            try:
                self.initial_cursors[paths[i]] = editor.textCursor()
            except AttributeError:
                pass

    def accept(self):
        self.is_visible = False
        QDialog.accept(self)
        self.list.clear()

    def restore_initial_state(self):
        """Restores initial cursors and initial active editor."""
        self.list.clear()
        self.is_visible = False
        widgets = self.widgets_by_path

        if not self.edit.clicked_outside:
            for path in self.initial_cursors:
                cursor = self.initial_cursors[path]
                if path in widgets:
                    self.set_editor_cursor(widgets[path], cursor)

            if self.initial_widget in self.paths_by_widget:
                index = self.paths.index(self.initial_path)
                self.sig_goto_file.emit(index)

    def set_dialog_position(self):
        """Positions the file switcher dialog."""
        parent = self.parent()
        geo = parent.geometry()
        width = self.list.width()  # This has been set in setup
        left = parent.geometry().width() / 2 - width / 2
        # Note: the +1 pixel on the top makes it look better
        if isinstance(parent, QMainWindow):
            top = (parent.toolbars_menu.geometry().height() +
                   parent.menuBar().geometry().height() + 1)
        else:
            top = self.plugins_tabs[0][0].tabBar().geometry().height() + 1

        while parent:
            geo = parent.geometry()
            top += geo.top()
            left += geo.left()
            parent = parent.parent()

        self.move(left, top)

    def get_item_size(self, content):
        """
        Get the max size (width and height) for the elements of a list of
        strings as a QLabel.
        """
        strings = []
        if content:
            for rich_text in content:
                label = QLabel(rich_text)
                label.setTextFormat(Qt.PlainText)
                strings.append(label.text())
                fm = label.fontMetrics()

            return (max([fm.width(s) * 1.3 for s in strings]), fm.height())

    def fix_size(self, content):
        """
        Adjusts the width and height of the file switcher
        based on the relative size of the parent and content.
        """
        # Update size of dialog based on relative size of the parent
        if content:
            width, height = self.get_item_size(content)

            # Width
            parent = self.parent()
            relative_width = parent.geometry().width() * 0.65
            if relative_width > self.MAX_WIDTH:
                relative_width = self.MAX_WIDTH
            self.list.setMinimumWidth(relative_width)

            # Height
            if len(content) < 8:
                max_entries = len(content)
            else:
                max_entries = 8
            max_height = height * max_entries * 2.5
            self.list.setMinimumHeight(max_height)

            # Resize
            self.list.resize(relative_width, self.list.height())

    # --- Helper methods: List widget
    def count(self):
        """Gets the item count in the list widget."""
        return self.list.count()

    def current_row(self):
        """Returns the current selected row in the list widget."""
        return self.list.currentRow()

    def set_current_row(self, row):
        """Sets the current selected row in the list widget."""
        return self.list.setCurrentRow(row)

    def select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def previous_row(self):
        """Select previous row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(-1)
            return
        prev_row = self.current_row() - 1
        if prev_row >= 0:
            title = self.list.item(prev_row).text()
        else:
            title = ''
        if prev_row == 0 and '</b></big><br>' in title:
            self.list.scrollToTop()
        elif '</b></big><br>' in title:
            # Select the next previous row, the one following is a title
            self.select_row(-2)
        else:
            self.select_row(-1)

    def next_row(self):
        """Select next row in list widget."""
        if self.mode == self.SYMBOL_MODE:
            self.select_row(+1)
            return
        next_row = self.current_row() + 1
        if next_row < self.count():
            if '</b></big><br>' in self.list.item(next_row).text():
                # Select the next next row, the one following is a title
                self.select_row(+2)
            else:
                self.select_row(+1)

    def get_stack_index(self, stack_index, plugin_index):
        """Get the real index of the selected item."""
        other_plugins_count = sum([other_tabs[0].count() \
                                   for other_tabs in \
                                   self.plugins_tabs[:plugin_index]])
        real_index = stack_index - other_plugins_count

        return real_index

    # --- Helper methods: Widget
    def get_plugin_data(self, plugin):
        """Get the data object of the plugin's current tab manager."""
        # The data object is named "data" in the editor plugin while it is
        # named "clients" in the notebook plugin.
        try:
            data = plugin.get_current_tab_manager().data
        except AttributeError:
            data = plugin.get_current_tab_manager().clients

        return data

    def get_plugin_tabwidget(self, plugin):
        """Get the tabwidget of the plugin's current tab manager."""
        # The tab widget is named "tabs" in the editor plugin while it is
        # named "tabwidget" in the notebook plugin.
        try:
            tabwidget = plugin.get_current_tab_manager().tabs
        except AttributeError:
            tabwidget = plugin.get_current_tab_manager().tabwidget

        return tabwidget

    def get_widget(self, index=None, path=None, tabs=None):
        """Get widget by index.

        If no tabs and index specified the current active widget is returned.
        """
        if (index and tabs) or (path and tabs):
            return tabs.widget(index)
        elif self.plugin:
            return self.get_plugin_tabwidget(self.plugin).currentWidget()
        else:
            return self.plugins_tabs[0][0].currentWidget()

    def set_editor_cursor(self, editor, cursor):
        """Set the cursor of an editor."""
        pos = cursor.position()
        anchor = cursor.anchor()

        new_cursor = QTextCursor()
        if pos == anchor:
            new_cursor.movePosition(pos)
        else:
            new_cursor.movePosition(anchor)
            new_cursor.movePosition(pos, QTextCursor.KeepAnchor)
        editor.setTextCursor(cursor)

    def goto_line(self, line_number):
        """Go to specified line number in current active editor."""
        if line_number:
            line_number = int(line_number)
            try:
                self.plugin.go_to_line(line_number)
            except AttributeError:
                pass

    # --- Helper methods: Outline explorer
    def get_symbol_list(self):
        """Get the list of symbols present in the file."""
        try:
            oedata = self.get_widget().get_outlineexplorer_data()
        except AttributeError:
            oedata = {}
        return oedata

    # --- Handlers
    def item_selection_changed(self):
        """List widget item selection change handler."""
        row = self.current_row()
        if self.count() and row >= 0:
            if '</b></big><br>' in self.list.currentItem().text() and row == 0:
                self.next_row()
            if self.mode == self.FILE_MODE:
                try:
                    stack_index = self.paths.index(self.filtered_path[row])
                    self.plugin = self.widgets[stack_index][1]
                    plugin_index = self.plugins_instances.index(self.plugin)
                    # Count the real index in the tabWidget of the
                    # current plugin
                    real_index = self.get_stack_index(stack_index,
                                                      plugin_index)
                    self.sig_goto_file.emit(
                        real_index, self.plugin.get_current_tab_manager())
                    self.goto_line(self.line_number)
                    try:
                        self.plugin.switch_to_plugin()
                        self.raise_()
                    except AttributeError:
                        # The widget using the fileswitcher is not a plugin
                        pass
                    self.edit.setFocus()
                except ValueError:
                    pass
            else:
                line_number = self.filtered_symbol_lines[row]
                self.goto_line(line_number)

    def setup_file_list(self, filter_text, current_path):
        """Setup list widget content for file list display."""
        short_paths = shorten_paths(self.paths, self.save_status)
        paths = self.paths
        icons = self.icons
        results = []
        trying_for_line_number = ':' in filter_text

        # Get optional line number
        if trying_for_line_number:
            filter_text, line_number = filter_text.split(':')
            if line_number == '':
                line_number = None
            # Get all the available filenames
            scores = get_search_scores('',
                                       self.filenames,
                                       template="<b>{0}</b>")
        else:
            line_number = None
            # Get all available filenames and get the scores for
            # "fuzzy" matching
            scores = get_search_scores(filter_text,
                                       self.filenames,
                                       template="<b>{0}</b>")

        # Get max width to determine if shortpaths should be used
        max_width = self.get_item_size(paths)[0]
        self.fix_size(paths)

        # Build the text that will appear on the list widget
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            if score_value != -1:
                text_item = '<big>' + rich_text.replace('&', '') + '</big>'
                if trying_for_line_number:
                    text_item += " [{0:} {1:}]".format(self.line_count[index],
                                                       _("lines"))
                if max_width > self.list.width():
                    text_item += u"<br><i>{0:}</i>".format(short_paths[index])
                else:
                    text_item += u"<br><i>{0:}</i>".format(paths[index])
                if (trying_for_line_number and self.line_count[index] != 0
                        or not trying_for_line_number):
                    results.append((score_value, index, text_item))

        # Sort the obtained scores and populate the list widget
        self.filtered_path = []
        plugin = None
        for result in sorted(results):
            index = result[1]
            path = paths[index]
            icon = icons[index]
            text = ''
            try:
                title = self.widgets[index][1].get_plugin_title().split(' - ')
                if plugin != title[0]:
                    plugin = title[0]
                    text += '<br><big><b>' + plugin + '</b></big><br>'
                    item = QListWidgetItem(text)
                    item.setToolTip(path)
                    item.setSizeHint(QSize(0, 25))
                    item.setFlags(Qt.ItemIsEditable)
                    self.list.addItem(item)
                    self.filtered_path.append(path)
            except:
                # The widget using the fileswitcher is not a plugin
                pass
            text = ''
            text += result[-1]
            item = QListWidgetItem(icon, text)
            item.setToolTip(path)
            item.setSizeHint(QSize(0, 25))
            self.list.addItem(item)
            self.filtered_path.append(path)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = True

        # Move selected item in list accordingly and update list size
        if current_path in self.filtered_path:
            self.set_current_row(self.filtered_path.index(current_path))
        elif self.filtered_path:
            self.set_current_row(0)

        # If a line number is searched look for it
        self.line_number = line_number
        self.goto_line(line_number)

    def setup_symbol_list(self, filter_text, current_path):
        """Setup list widget content for symbol list display."""
        # Get optional symbol name
        filter_text, symbol_text = filter_text.split('@')

        # Fetch the Outline explorer data, get the icons and values
        oedata = self.get_symbol_list()
        icons = get_python_symbol_icons(oedata)

        # The list of paths here is needed in order to have the same
        # point of measurement for the list widget size as in the file list
        # See issue 4648
        paths = self.paths
        # Update list size
        self.fix_size(paths)

        symbol_list = process_python_symbol_data(oedata)
        line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list]
        choices = [item[1] for item in symbol_list]
        scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        results = []
        lines = []
        self.filtered_symbol_lines = []
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            line, fold_level, token = line_fold_token[index]
            lines.append(text)
            if score_value != -1:
                results.append((score_value, line, text, rich_text, fold_level,
                                icons[index], token))

        template = '{0}{1}'

        for (score, line, text, rich_text, fold_level, icon,
             token) in sorted(results):
            fold_space = '&nbsp;' * (fold_level)
            line_number = line + 1
            self.filtered_symbol_lines.append(line_number)
            textline = template.format(fold_space, rich_text)
            item = QListWidgetItem(icon, textline)
            item.setSizeHint(QSize(0, 16))
            self.list.addItem(item)

        # To adjust the delegate layout for KDE themes
        self.list.files_list = False

        # Select edit line when using symbol search initially.
        # See issue 5661
        self.edit.setFocus()

        # Move selected item in list accordingly
        # NOTE: Doing this is causing two problems:
        # 1. It makes the cursor to auto-jump to the last selected
        #    symbol after opening or closing a different file
        # 2. It moves the cursor to the first symbol by default,
        #    which is very distracting.
        # That's why this line is commented!
        # self.set_current_row(0)

    def setup(self):
        """Setup list widget content."""
        if len(self.plugins_tabs) == 0:
            self.close()
            return

        self.list.clear()
        current_path = self.current_path
        filter_text = self.filter_text

        # Get optional line or symbol to define mode and method handler
        trying_for_symbol = ('@' in self.filter_text)

        if trying_for_symbol:
            self.mode = self.SYMBOL_MODE
            self.setup_symbol_list(filter_text, current_path)
        else:
            self.mode = self.FILE_MODE
            self.setup_file_list(filter_text, current_path)

        # Set position according to size
        self.set_dialog_position()

    def show(self):
        """
        Override Qt method to force an update of the fileswitcher before
        showing it. See Issue #5317 and PR #5389.
        """
        self.setup()
        super(FileSwitcher, self).show()

    def add_plugin(self, plugin, tabs, data, icon):
        """Add a plugin to display its files."""
        self.plugins_tabs.append((tabs, plugin))
        self.plugins_data.append((data, icon))
        self.plugins_instances.append(plugin)
コード例 #28
0
ファイル: main.py プロジェクト: lnls-sirius/hla
class MacReportWindow(SiriusMainWindow):
    """Machine Report Window."""
    def __init__(self, parent=None):
        """Init."""
        super().__init__(parent)
        self._macreport = MacReport()
        self._macreport.connector.timeout = 5 * 60

        self.setWindowIcon(qta.icon('fa.book', color='gray'))
        self.setWindowTitle('Machine Reports')

        self._fsi = '{:8d}'
        self._fs1 = '{:8.3f}'
        self._fs2 = '{:8.3f} ± {:8.3f}'
        self._fst1 = '{:02d}h{:02d}'
        self._fst2 = '{:02d}h{:02d} ± {:02d}h{:02d}'

        self._update_task = None

        self._setupUi()
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFocus(True)

    def _setupUi(self):
        cwid = QWidget(self)
        self.setCentralWidget(cwid)

        title = QLabel('<h3>Machine Reports</h3>',
                       self,
                       alignment=Qt.AlignCenter)

        self._timesel_gbox = self._setupTimePeriodSelWidget()
        self._timesel_gbox.setObjectName('timesel_gbox')
        self._timesel_gbox.setStyleSheet(
            "#timesel_gbox{min-height: 8em; max-height: 8em;}")

        self._progress_list = QListWidget(self)
        self._progress_list.setObjectName('progress_list')
        self._progress_list.setStyleSheet(
            "#progress_list{min-height: 8em; max-height: 8em;}")

        self._reports_wid = QTabWidget(cwid)
        self._reports_wid.setObjectName('ASTab')
        self._reports_wid.addTab(self._setupUserShiftStatsWidget(),
                                 'User Shift Stats')
        self._reports_wid.addTab(self._setupLightSourceUsageStats(),
                                 'Light Source Usage Stats')
        self._reports_wid.addTab(self._setupStoredCurrentStats(),
                                 'Stored Current Stats')

        self._pb_showraw = QPushButton(qta.icon('mdi.chart-line'),
                                       'Show Raw Data', self)
        self._pb_showraw.setEnabled(False)
        self._pb_showraw.clicked.connect(self._show_raw_data)

        self._pb_showpvsd = QPushButton(qta.icon('mdi.chart-line'),
                                        'Show Progrmd.vs.Delivered Hours',
                                        self)
        self._pb_showpvsd.setEnabled(False)
        self._pb_showpvsd.clicked.connect(self._show_progmd_vs_delivd)

        lay = QGridLayout(cwid)
        lay.setVerticalSpacing(10)
        lay.setHorizontalSpacing(10)
        lay.setContentsMargins(18, 9, 18, 9)
        lay.addWidget(title, 0, 0, 1, 3)
        lay.addWidget(self._timesel_gbox, 1, 0)
        lay.addWidget(self._progress_list,
                      1,
                      1,
                      1,
                      2,
                      alignment=Qt.AlignBottom)
        lay.addWidget(self._reports_wid, 2, 0, 1, 3)
        lay.addWidget(self._pb_showpvsd, 4, 0, alignment=Qt.AlignLeft)
        lay.addWidget(self._pb_showraw, 4, 2, alignment=Qt.AlignRight)

        self._updateUserShiftStats(setup=True)
        self._updateStoredCurrentStats(setup=True)
        self._updateLightSourceUsageStats(setup=True)

    def _setupTimePeriodSelWidget(self):
        tnow = Time.now()

        ld_tstart = QLabel('Time start: ')
        self.dt_start = QDateTimeEdit(tnow - 10 * 60, self)
        self.dt_start.setCalendarPopup(True)
        self.dt_start.setMinimumDate(Time(2020, 1, 1))
        self.dt_start.setDisplayFormat('dd/MM/yyyy hh:mm')

        ld_tstop = QLabel('Time stop: ')
        self.dt_stop = QDateTimeEdit(tnow, self)
        self.dt_stop.setCalendarPopup(True)
        self.dt_stop.setMinimumDate(Time(2020, 1, 1))
        self.dt_stop.setDisplayFormat('dd/MM/yyyy hh:mm')

        self.pb_search = QPushButton(qta.icon('fa5s.search'), 'Search', self)
        self.pb_search.clicked.connect(self._do_update)
        self.pb_search.setObjectName('pb_search')
        self.pb_search.setStyleSheet("""
            #pb_search{
                min-width:100px; max-width:100px;
                min-height:25px; max-height:25px;
                icon-size:20px;}
        """)

        wid = QGroupBox('Select interval: ', self)
        lay = QGridLayout(wid)
        lay.addWidget(ld_tstart, 0, 0)
        lay.addWidget(self.dt_start, 0, 1)
        lay.addWidget(ld_tstop, 1, 0)
        lay.addWidget(self.dt_stop, 1, 1)
        lay.addWidget(self.pb_search, 2, 1, alignment=Qt.AlignRight)
        return wid

    def _setupUserShiftStatsWidget(self):
        self.lb_uspt = LbData('')
        self.lb_usdt = LbData('')
        self.lb_ustt = LbData('')
        self.lb_uset = LbData('')
        self.lb_uspc = LbData('')
        self.lb_cav = LbData('')
        self.lb_cbav = LbData('')
        self.lb_ceav = LbData('')
        self.lb_tft = LbData('')
        self.lb_bdc = LbData('')
        self.lb_mttr = LbData('')
        self.lb_mtbf = LbData('')
        self.lb_reli = LbData('')
        self.lb_tsbt = LbData('')
        self.lb_tubt = LbData('')
        self.lb_mtbu = LbData('')
        self.lb_rsbt = LbData('')
        self.lb_itav = LbData('')

        wid = QWidget(self)
        lay = QGridLayout(wid)
        lay.setVerticalSpacing(0)
        lay.setHorizontalSpacing(0)
        lay.setAlignment(Qt.AlignTop)
        lay.addItem(QSpacerItem(120, 1, QSzPlcy.Fixed, QSzPlcy.Ignored), 0, 0)
        lay.addWidget(LbHHeader('Programmed Time (h)'), 0, 1)
        lay.addWidget(self.lb_uspt, 0, 2)
        lay.addWidget(LbHHeader('Delivered Time (h)'), 1, 1)
        lay.addWidget(self.lb_usdt, 1, 2)
        lay.addWidget(LbHHeader('Total Time (h)'), 2, 1)
        lay.addWidget(self.lb_ustt, 2, 2)
        lay.addWidget(LbHHeader('Extra Time (h)'), 3, 1)
        lay.addWidget(self.lb_uset, 3, 2)
        lay.addWidget(LbHHeader('# Programmed Shifts'), 4, 1)
        lay.addWidget(self.lb_uspc, 4, 2)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA)'), 5, 1)
        lay.addWidget(self.lb_cav, 5, 2)
        lay.addWidget(
            LbHHeader('Current at the Beg. of the Shift (avg ± std) (mA)'), 6,
            1)
        lay.addWidget(self.lb_cbav, 6, 2)
        lay.addWidget(
            LbHHeader('Current at the End of the Shift (avg ± std) (mA)'), 7,
            1)
        lay.addWidget(self.lb_ceav, 7, 2)
        lay.addWidget(LbHHeader('Total Failures Time (h)'), 8, 1)
        lay.addWidget(self.lb_tft, 8, 2)
        lay.addWidget(LbHHeader('# Beam Dumps'), 9, 1)
        lay.addWidget(self.lb_bdc, 9, 2)
        lay.addWidget(LbHHeader('Time To Recover (avg ± std) (h)'), 10, 1)
        lay.addWidget(self.lb_mttr, 10, 2)
        lay.addWidget(LbHHeader('Time Between Failures (avg) (h)'), 11, 1)
        lay.addWidget(self.lb_mtbf, 11, 2)
        lay.addWidget(LbHHeader('Beam Reliability (%)'), 12, 1)
        lay.addWidget(self.lb_reli, 12, 2)
        lay.addWidget(LbHHeader('Total stable beam time (h)'), 13, 1)
        lay.addWidget(self.lb_tsbt, 13, 2)
        lay.addWidget(LbHHeader('Total unstable beam time (h)'), 14, 1)
        lay.addWidget(self.lb_tubt, 14, 2)
        lay.addWidget(LbHHeader('Time between unstable beams (avg) (h)'), 15,
                      1)
        lay.addWidget(self.lb_mtbu, 15, 2)
        lay.addWidget(LbHHeader('Relative stable beam time (%)'), 16, 1)
        lay.addWidget(self.lb_rsbt, 16, 2)
        lay.addWidget(LbHHeader('Injection time (avg ± std) (h)'), 17, 1)
        lay.addWidget(self.lb_itav, 17, 2)
        lay.addItem(QSpacerItem(120, 1, QSzPlcy.Fixed, QSzPlcy.Ignored), 0, 3)
        return wid

    def _updateUserShiftStats(self, setup=False):
        w2r = {
            'uspt': [
                'usershift_progmd_time',
            ],
            'usdt': [
                'usershift_delivd_time',
            ],
            'ustt': [
                'usershift_total_time',
            ],
            'uset': [
                'usershift_extra_time',
            ],
            'uspc': [
                'usershift_progmd_count',
            ],
            'cav': ['usershift_current_average', 'usershift_current_stddev'],
            'cbav':
            ['usershift_current_beg_average', 'usershift_current_beg_stddev'],
            'ceav':
            ['usershift_current_end_average', 'usershift_current_end_stddev'],
            'tft': [
                'usershift_total_failures_time',
            ],
            'bdc': [
                'usershift_beam_dump_count',
            ],
            'mttr': [
                'usershift_time_to_recover_average',
                'usershift_time_to_recover_stddev'
            ],
            'mtbf': [
                'usershift_time_between_failures_average',
            ],
            'reli': [
                'usershift_beam_reliability',
            ],
            'tsbt': [
                'usershift_total_stable_beam_time',
            ],
            'tubt': [
                'usershift_total_unstable_beam_time',
            ],
            'mtbu': ['usershift_time_between_unstable_beams_average'],
            'rsbt': ['usershift_relative_stable_beam_time'],
            'itav': [
                'usershift_injection_time_average',
                'usershift_injection_time_stddev'
            ]
        }

        for wname, rname in w2r.items():
            wid = getattr(self, 'lb_' + wname)
            items = [getattr(self._macreport, n) for n in rname]
            if 'time' in rname[0] and 'relative' not in rname[0]:
                if len(items) == 2:
                    if items[0] not in [None, _np.inf]:
                        hour1 = int(items[0])
                        minu1 = int((items[0] - hour1) * 60)
                        hour2 = int(items[1])
                        minu2 = int((items[1] - hour2) * 60)
                        items = [hour1, minu1, hour2, minu2]
                        str2fmt = self._fst2
                    else:
                        str2fmt = self._fs1
                elif items[0] not in [None, _np.inf]:
                    hour = int(items[0])
                    minu = int((items[0] - hour) * 60)
                    items = [hour, minu]
                    str2fmt = self._fst1
                else:
                    str2fmt = self._fs1
            else:
                str2fmt = getattr(
                    self,
                    '_fsi' if 'count' in rname[0] else '_fs' + str(len(rname)))
            text = '' if any([i is None for i in items]) \
                else str2fmt.format(*items)
            wid.setText(text)
            if setup:
                wid.setToolTip(getattr(MacReport, rname[0]).__doc__)

    def _setupStoredCurrentStats(self):
        self.lb_user_mb_avg = LbData('')
        self.lb_user_mb_intvl = LbData('')
        self.lb_user_sb_avg = LbData('')
        self.lb_user_sb_intvl = LbData('')
        self.lb_user_tt_avg = LbData('')
        self.lb_user_tt_intvl = LbData('')
        self.lb_commi_mb_avg = LbData('')
        self.lb_commi_mb_intvl = LbData('')
        self.lb_commi_sb_avg = LbData('')
        self.lb_commi_sb_intvl = LbData('')
        self.lb_commi_tt_avg = LbData('')
        self.lb_commi_tt_intvl = LbData('')
        self.lb_condi_mb_avg = LbData('')
        self.lb_condi_mb_intvl = LbData('')
        self.lb_condi_sb_avg = LbData('')
        self.lb_condi_sb_intvl = LbData('')
        self.lb_condi_tt_avg = LbData('')
        self.lb_condi_tt_intvl = LbData('')
        self.lb_mstdy_mb_avg = LbData('')
        self.lb_mstdy_mb_intvl = LbData('')
        self.lb_mstdy_sb_avg = LbData('')
        self.lb_mstdy_sb_intvl = LbData('')
        self.lb_mstdy_tt_avg = LbData('')
        self.lb_mstdy_tt_intvl = LbData('')
        self.lb_stord_mb_avg = LbData('')
        self.lb_stord_mb_intvl = LbData('')
        self.lb_stord_sb_avg = LbData('')
        self.lb_stord_sb_intvl = LbData('')
        self.lb_stord_tt_avg = LbData('')
        self.lb_stord_tt_intvl = LbData('')

        wid = QWidget(self)
        lay = QGridLayout(wid)
        lay.setVerticalSpacing(0)
        lay.setHorizontalSpacing(0)
        lay.setAlignment(Qt.AlignTop)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA) (MB)'), 1, 0)
        lay.addWidget(LbHHeader('Time in MB mode (h)'), 2, 0)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA) (SB)'), 3, 0)
        lay.addWidget(LbHHeader('Time in SB mode (h)'), 4, 0)
        lay.addWidget(LbHHeader('Current (avg ± std) (mA) (SB+MB)'), 5, 0)
        lay.addWidget(LbHHeader('Total Time (h) (SB+MB)'), 6, 0)
        lay.addWidget(LbVHeader('Users'), 0, 1)
        lay.addWidget(self.lb_user_mb_avg, 1, 1)
        lay.addWidget(self.lb_user_mb_intvl, 2, 1)
        lay.addWidget(self.lb_user_sb_avg, 3, 1)
        lay.addWidget(self.lb_user_sb_intvl, 4, 1)
        lay.addWidget(self.lb_user_tt_avg, 5, 1)
        lay.addWidget(self.lb_user_tt_intvl, 6, 1)
        lay.addWidget(LbVHeader('Commissioning'), 0, 2)
        lay.addWidget(self.lb_commi_mb_avg, 1, 2)
        lay.addWidget(self.lb_commi_mb_intvl, 2, 2)
        lay.addWidget(self.lb_commi_sb_avg, 3, 2)
        lay.addWidget(self.lb_commi_sb_intvl, 4, 2)
        lay.addWidget(self.lb_commi_tt_avg, 5, 2)
        lay.addWidget(self.lb_commi_tt_intvl, 6, 2)
        lay.addWidget(LbVHeader('Conditioning'), 0, 3)
        lay.addWidget(self.lb_condi_mb_avg, 1, 3)
        lay.addWidget(self.lb_condi_mb_intvl, 2, 3)
        lay.addWidget(self.lb_condi_sb_avg, 3, 3)
        lay.addWidget(self.lb_condi_sb_intvl, 4, 3)
        lay.addWidget(self.lb_condi_tt_avg, 5, 3)
        lay.addWidget(self.lb_condi_tt_intvl, 6, 3)
        lay.addWidget(LbVHeader('Machine Study'), 0, 4)
        lay.addWidget(self.lb_mstdy_mb_avg, 1, 4)
        lay.addWidget(self.lb_mstdy_mb_intvl, 2, 4)
        lay.addWidget(self.lb_mstdy_sb_avg, 3, 4)
        lay.addWidget(self.lb_mstdy_sb_intvl, 4, 4)
        lay.addWidget(self.lb_mstdy_tt_avg, 5, 4)
        lay.addWidget(self.lb_mstdy_tt_intvl, 6, 4)
        lay.addWidget(LbVHeader('All Stored Beam'), 0, 5)
        lay.addWidget(self.lb_stord_mb_avg, 1, 5)
        lay.addWidget(self.lb_stord_mb_intvl, 2, 5)
        lay.addWidget(self.lb_stord_sb_avg, 3, 5)
        lay.addWidget(self.lb_stord_sb_intvl, 4, 5)
        lay.addWidget(self.lb_stord_tt_avg, 5, 5)
        lay.addWidget(self.lb_stord_tt_intvl, 6, 5)
        return wid

    def _updateStoredCurrentStats(self, setup=False):
        shifttype = {
            'mstdy': 'machinestudy',
            'commi': 'commissioning',
            'condi': 'conditioning',
            'stord': 'ebeam',
            'user': '******'
        }
        fillmode = {'mb': 'multibunch', 'sb': 'singlebunch', 'tt': 'total'}
        stats = {
            'avg': ['average', 'stddev'],
            'intvl': [
                'time',
            ]
        }

        for wsht, rsht in shifttype.items():
            for wfm, rfm in fillmode.items():
                for wstt, rstt in stats.items():
                    wid = getattr(self, 'lb_' + wsht + '_' + wfm + '_' + wstt)
                    pname = 'current_' + rsht + '_' + rfm + '_'
                    items = [getattr(self._macreport, pname + i) for i in rstt]
                    if 'time' in rstt[0] and items[0] is not None:
                        hour = int(items[0])
                        minu = int((items[0] - hour) * 60)
                        items = [hour, minu]
                    str2fmt = getattr(
                        self, '_fst1' if
                        ('time' in rstt[0]) else '_fs' + str(len(rstt)))
                    text = '' if any([i is None for i in items]) \
                        else str2fmt.format(*items)
                    wid.setText(text)
                    if setup:
                        wid.setToolTip(
                            getattr(MacReport, pname + rstt[0]).__doc__)

    def _setupLightSourceUsageStats(self):
        self.lb_mstdy_fail_intvl = LbData('')
        self.lb_mstdy_fail_pcntl = LbData('')
        self.lb_mstdy_oper_intvl = LbData('')
        self.lb_mstdy_oper_pcntl = LbData('')
        self.lb_mstdy_total_intvl = LbData('')
        self.lb_mstdy_total_pcntl = LbData('')
        self.lb_commi_fail_intvl = LbData('')
        self.lb_commi_fail_pcntl = LbData('')
        self.lb_commi_oper_intvl = LbData('')
        self.lb_commi_oper_pcntl = LbData('')
        self.lb_commi_total_intvl = LbData('')
        self.lb_commi_total_pcntl = LbData('')
        self.lb_condi_fail_intvl = LbData('')
        self.lb_condi_fail_pcntl = LbData('')
        self.lb_condi_oper_intvl = LbData('')
        self.lb_condi_oper_pcntl = LbData('')
        self.lb_condi_total_intvl = LbData('')
        self.lb_condi_total_pcntl = LbData('')
        self.lb_maint_fail_intvl = LbData('')
        self.lb_maint_fail_pcntl = LbData('')
        self.lb_maint_oper_intvl = LbData('')
        self.lb_maint_oper_pcntl = LbData('')
        self.lb_maint_total_intvl = LbData('')
        self.lb_maint_total_pcntl = LbData('')
        self.lb_user_fail_intvl = LbData('')
        self.lb_user_fail_pcntl = LbData('')
        self.lb_user_oper_intvl = LbData('')
        self.lb_user_oper_pcntl = LbData('')
        self.lb_user_total_intvl = LbData('')
        self.lb_user_total_pcntl = LbData('')
        self.lb_total_intvl = LbHHeader('Total Usage Time (h): - ')

        wid = QWidget(self)
        lay = QGridLayout(wid)
        lay.setVerticalSpacing(0)
        lay.setHorizontalSpacing(0)
        lay.setAlignment(Qt.AlignTop)
        lay.addWidget(LbHHeader('Operational Time (h)'), 1, 0)
        lay.addWidget(LbHHeader('Operational Percentage (%)'), 2, 0)
        lay.addWidget(LbHHeader('Failures Time (h)'), 3, 0)
        lay.addWidget(LbHHeader('Failures Percentage (%)'), 4, 0)
        lay.addWidget(LbHHeader('Shift Time (h)'), 5, 0)
        lay.addWidget(LbHHeader('Shift Percentage (%)'), 6, 0)
        lay.addWidget(self.lb_total_intvl, 7, 0, 1, 6)
        lay.addWidget(LbVHeader('Users'), 0, 1)
        lay.addWidget(self.lb_user_oper_intvl, 1, 1)
        lay.addWidget(self.lb_user_oper_pcntl, 2, 1)
        lay.addWidget(self.lb_user_fail_intvl, 3, 1)
        lay.addWidget(self.lb_user_fail_pcntl, 4, 1)
        lay.addWidget(self.lb_user_total_intvl, 5, 1)
        lay.addWidget(self.lb_user_total_pcntl, 6, 1)
        lay.addWidget(LbVHeader('Commissioning'), 0, 2)
        lay.addWidget(self.lb_commi_oper_intvl, 1, 2)
        lay.addWidget(self.lb_commi_oper_pcntl, 2, 2)
        lay.addWidget(self.lb_commi_fail_intvl, 3, 2)
        lay.addWidget(self.lb_commi_fail_pcntl, 4, 2)
        lay.addWidget(self.lb_commi_total_intvl, 5, 2)
        lay.addWidget(self.lb_commi_total_pcntl, 6, 2)
        lay.addWidget(LbVHeader('Conditioning'), 0, 3)
        lay.addWidget(self.lb_condi_oper_intvl, 1, 3)
        lay.addWidget(self.lb_condi_oper_pcntl, 2, 3)
        lay.addWidget(self.lb_condi_fail_intvl, 3, 3)
        lay.addWidget(self.lb_condi_fail_pcntl, 4, 3)
        lay.addWidget(self.lb_condi_total_intvl, 5, 3)
        lay.addWidget(self.lb_condi_total_pcntl, 6, 3)
        lay.addWidget(LbVHeader('Machine Study'), 0, 4)
        lay.addWidget(self.lb_mstdy_oper_intvl, 1, 4)
        lay.addWidget(self.lb_mstdy_oper_pcntl, 2, 4)
        lay.addWidget(self.lb_mstdy_fail_intvl, 3, 4)
        lay.addWidget(self.lb_mstdy_fail_pcntl, 4, 4)
        lay.addWidget(self.lb_mstdy_total_intvl, 5, 4)
        lay.addWidget(self.lb_mstdy_total_pcntl, 6, 4)
        lay.addWidget(LbVHeader('Maintenance'), 0, 5)
        lay.addWidget(self.lb_maint_oper_intvl, 1, 5)
        lay.addWidget(self.lb_maint_oper_pcntl, 2, 5)
        lay.addWidget(self.lb_maint_fail_intvl, 3, 5)
        lay.addWidget(self.lb_maint_fail_pcntl, 4, 5)
        lay.addWidget(self.lb_maint_total_intvl, 5, 5)
        lay.addWidget(self.lb_maint_total_pcntl, 6, 5)
        return wid

    def _updateLightSourceUsageStats(self, setup=False):
        shifttype = {
            'mstdy': 'machinestudy',
            'commi': 'commissioning',
            'condi': 'conditioning',
            'maint': 'maintenance',
            'user': '******'
        }
        intervaltype = {
            'fail': '_failures',
            'oper': '_operational',
            'total': ''
        }
        for wst, rst in shifttype.items():
            for wit, rit in intervaltype.items():
                widt = getattr(self, 'lb_' + wst + '_' + wit + '_intvl')
                tname = 'lsusage_' + rst + rit + '_time'
                tval = getattr(self._macreport, tname)
                if tval is None:
                    text = ''
                else:
                    hour = int(tval)
                    minu = int((tval - hour) * 60)
                    text = self._fst1.format(hour, minu)
                widt.setText(text)
                if setup:
                    widt.setToolTip(getattr(MacReport, tname).__doc__)

                widp = getattr(self, 'lb_' + wst + '_' + wit + '_pcntl')
                pname = 'lsusage_' + rst + rit
                pval = getattr(self._macreport, pname)
                text = '' if pval is None else self._fs1.format(pval)
                widp.setText(text)
                if setup:
                    widp.setToolTip(getattr(MacReport, pname).__doc__)

        text = 'Total Usage Time (h): '
        if self._macreport.lsusage_total_time is not None:
            val = self._macreport.lsusage_total_time
            hour = int(val)
            minu = int((val - hour) * 60)
            text += self._fst1.format(hour, minu)
        self.lb_total_intvl.setText(text)

    def _do_update(self):
        if self.sender().text() == 'Abort':
            self._update_task.terminate()
            now = Time.now().strftime('%Y/%m/%d-%H:%M:%S')
            item = QListWidgetItem(now + '  Aborted.')
            self._progress_list.addItem(item)
            self._progress_list.scrollToBottom()
            self._setup_search_button()
        else:
            if self.dt_start.dateTime() >= self.dt_stop.dateTime() or \
                    self.dt_start.dateTime() > Time.now() or \
                    self.dt_stop.dateTime() > Time.now():
                QMessageBox.warning(self, 'Ops...',
                                    'Insert a valid time interval.')
                return

            self._macreport.timestamp_start = \
                self.dt_start.dateTime().toSecsSinceEpoch()
            self._macreport.timestamp_stop = \
                self.dt_stop.dateTime().toSecsSinceEpoch()

            self._progress_list.clear()
            self._pb_showraw.setEnabled(False)
            self._pb_showpvsd.setEnabled(False)
            self._setup_search_button()

            self._update_task = UpdateTask(self._macreport)
            self._update_task.updated.connect(self._update_progress)
            self._update_task.start()

    def _update_progress(self, message):
        item = QListWidgetItem(message)
        self._progress_list.addItem(item)
        self._progress_list.scrollToBottom()

        if 'Collected' in message:
            self._setup_search_button()
            self._updateUserShiftStats()
            self._updateStoredCurrentStats()
            self._updateLightSourceUsageStats()
            self._pb_showraw.setEnabled(True)
            self._pb_showpvsd.setEnabled(True)

    def _setup_search_button(self):
        if self.pb_search.text() == 'Abort':
            self.pb_search.setIcon(qta.icon('fa5s.search'))
            self.pb_search.setText('Search')
        else:
            self.pb_search.setIcon(
                qta.icon('fa5s.spinner', animation=qta.Spin(self.pb_search)))
            self.pb_search.setText('Abort')

    def _show_raw_data(self):
        fig = self._macreport.plot_raw_data()
        wid = MatplotlibWidget(fig)
        wid.setWindowTitle('Machine Reports - Raw Data (' +
                           str(self._macreport.time_start) + ' -> ' +
                           str(self._macreport.time_stop) + ')')
        wid.show()

    def _show_progmd_vs_delivd(self):
        fig = self._macreport.plot_progmd_vs_delivd_hours()
        wid = MatplotlibWidget(fig)
        wid.setWindowTitle(
            'Machine Reports - Programmed vs. Delivered Hours (' +
            str(self._macreport.time_start) + ' -> ' +
            str(self._macreport.time_stop) + ')')
        wid.show()
コード例 #29
0
 def refresh_profiles(list_widget: QListWidget, new_values: typing.List[str], index: int):
     list_widget.clear()
     list_widget.addItems(new_values)
     if index != -1:
         list_widget.setCurrentRow(index)
コード例 #30
0
class PSTestWindow(SiriusMainWindow):
    """PS test window."""
    def __init__(self, parent=None, adv_mode=False):
        """Constructor."""
        super().__init__(parent)
        self.setWindowTitle('PS/PU Test')
        self.setObjectName('ASApp')
        cor = get_appropriate_color(section='AS')
        self.setWindowIcon(qta.icon('mdi.test-tube', color=cor))

        # auxiliar data for initializing SI Fam PS
        self._is_adv_mode = adv_mode
        self._si_fam_psnames = PSSearch.get_psnames(filters={
            'sec': 'SI',
            'sub': 'Fam',
            'dis': 'PS'
        })

        # auxiliary data for SI fast correctors
        self._si_fastcorrs = PSSearch.get_psnames(filters={
            'sec': 'SI',
            'dis': 'PS',
            'dev': 'FC.*'
        })

        self._needs_update_setup = False
        self._setup_ui()
        self._update_setup_timer = QTimer(self)
        self._update_setup_timer.timeout.connect(self._update_setup)
        self._update_setup_timer.setInterval(250)
        self._update_setup_timer.start()

    def _setup_ui(self):
        # setup central widget
        self.central_widget = QFrame()
        self.central_widget.setStyleSheet("""
            #OkList {
                background-color: #eafaea;
            }
            #NokList {
                background-color: #ffebe6;
            }
            QLabel{
                max-height: 1.29em;
            }
            QTabWidget::pane {
                border-left: 2px solid gray;
                border-bottom: 2px solid gray;
                border-right: 2px solid gray;
            }""")
        self.setCentralWidget(self.central_widget)

        self.tab = QTabWidget(self)
        self.tab.setObjectName('ASTab')

        # # PS
        self.ps_wid = QWidget(self)
        lay_ps = QGridLayout(self.ps_wid)
        lay_ps.setContentsMargins(0, 9, 0, 0)
        lay_ps.setHorizontalSpacing(0)

        # PS selection
        self.ps_tree = PVNameTree(items=self._get_ps_tree_names(),
                                  tree_levels=('sec', 'mag_group'),
                                  parent=self)
        self.ps_tree.tree.setHeaderHidden(True)
        self.ps_tree.setSizePolicy(QSizePolicy.Preferred,
                                   QSizePolicy.Preferred)
        self.ps_tree.tree.setColumnCount(1)
        self.ps_tree.tree.doubleClicked.connect(self._open_detail)
        self.ps_tree.tree.itemChanged.connect(
            self._handle_checked_items_changed)

        gbox_ps_select = QGroupBox('Select PS: ', self)
        gbox_ps_select.setObjectName('select')
        gbox_ps_select.setStyleSheet("""
            #select{
                border-top: 0px solid transparent;
                border-left: 0px solid transparent;
                border-bottom: 0px solid transparent;
            }""")
        lay_ps_select = QVBoxLayout(gbox_ps_select)
        lay_ps_select.addWidget(self.ps_tree)
        lay_ps.addWidget(gbox_ps_select, 0, 0)
        lay_ps.setColumnStretch(0, 1)

        # PS commands
        self.checkcomm_ps_bt = QPushButton('Check Communication', self)
        self.checkcomm_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.checkcomm_ps_bt.clicked.connect(self._check_comm)
        self.checkcomm_ps_bt.setToolTip(
            'Check PS and DCLinks communication status (verify invalid alarms '
            'and, if LI, the value of Connected-Mon PV)')

        self.checkstatus_ps_bt = QPushButton('Show Status Summary', self)
        self.checkstatus_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.checkstatus_ps_bt.clicked.connect(_part(self._check_status, 'PS'))
        self.checkstatus_ps_bt.setToolTip(
            'Check PS and DCLinks interlock status and, if powered on, '
            'check if it is following reference')

        self.dsbltrigger_ps_bt = QPushButton('Disable PS triggers', self)
        self.dsbltrigger_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.dsbltrigger_ps_bt.clicked.connect(
            _part(self._set_check_trigger_state, 'PS', 'dsbl'))

        self.setsofbmode_ps_bt = QPushButton('Turn Off SOFBMode', self)
        self.setsofbmode_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.setsofbmode_ps_bt.clicked.connect(
            _part(self._set_check_fbp_sofbmode, 'off'))

        self.setslowref_ps_bt = QPushButton('Set PS and DCLinks to SlowRef',
                                            self)
        self.setslowref_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.setslowref_ps_bt.clicked.connect(self._set_check_opmode_slowref)

        self.currzero_ps_bt1 = QPushButton('Set PS Current to zero', self)
        self.currzero_ps_bt1.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.currzero_ps_bt1.clicked.connect(self._set_zero_ps)

        self.reset_ps_bt = QPushButton('Reset PS and DCLinks', self)
        self.reset_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.reset_ps_bt.clicked.connect(_part(self._reset_intlk, 'PS'))

        self.prep_sidclink_bt = QPushButton('Prepare SI Fam DCLinks', self)
        self.prep_sidclink_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.prep_sidclink_bt.clicked.connect(self._prepare_sidclinks)
        self.prep_sidclink_bt.setVisible(False)

        self.init_sips_bt = QPushButton('Initialize SI Fam PS', self)
        self.init_sips_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.init_sips_bt.clicked.connect(self._set_check_pwrstateinit)
        self.init_sips_bt.setVisible(False)

        self.aux_label = QLabel('')
        self.aux_label.setVisible(False)

        self.turnon_dcl_bt = QPushButton('Turn DCLinks On', self)
        self.turnon_dcl_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.turnon_dcl_bt.clicked.connect(
            _part(self._set_check_pwrstate_dclinks, 'on'))

        self.checkctrlloop_dcl_bt = QPushButton('Check DCLinks CtrlLoop', self)
        self.checkctrlloop_dcl_bt.clicked.connect(
            _part(self._set_lastcomm, 'PS'))
        self.checkctrlloop_dcl_bt.clicked.connect(
            _part(self._set_check_ctrlloop, 'dclink'))

        self.setvolt_dcl_bt = QPushButton('Set DCLinks Voltage', self)
        self.setvolt_dcl_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.setvolt_dcl_bt.clicked.connect(self._set_check_dclinks_capvolt)

        self.turnon_ps_bt = QPushButton('Turn PS On', self)
        self.turnon_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.turnon_ps_bt.clicked.connect(
            _part(self._set_check_pwrstate, 'PS', 'on', True))

        self.setcheckctrlloop_ps_bt = QPushButton('Check PS CtrlLoop', self)
        self.setcheckctrlloop_ps_bt.clicked.connect(
            _part(self._set_lastcomm, 'PS'))
        self.setcheckctrlloop_ps_bt.clicked.connect(
            _part(self._set_check_ctrlloop, 'pwrsupply'))

        self.test_ps_bt = QPushButton('Set PS Current to test value', self)
        self.test_ps_bt.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.test_ps_bt.clicked.connect(self._set_test_ps)

        self.currzero_ps_bt2 = QPushButton('Set PS Current to zero', self)
        self.currzero_ps_bt2.clicked.connect(_part(self._set_lastcomm, 'PS'))
        self.currzero_ps_bt2.clicked.connect(self._set_zero_ps)

        self.restoretrigger_ps_bt = QPushButton('Restore PS triggers', self)
        self.restoretrigger_ps_bt.clicked.connect(
            _part(self._set_lastcomm, 'PS'))
        self.restoretrigger_ps_bt.clicked.connect(
            _part(self._restore_triggers_state, 'PS'))

        gbox_ps_comm = QGroupBox('Commands', self)
        gbox_ps_comm.setObjectName('comm')
        gbox_ps_comm.setStyleSheet('#comm{border: 0px solid transparent;}')
        lay_ps_comm = QVBoxLayout(gbox_ps_comm)
        lay_ps_comm.setContentsMargins(20, 9, 20, 9)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Check</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.checkcomm_ps_bt)
        lay_ps_comm.addWidget(self.checkstatus_ps_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Prepare</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.dsbltrigger_ps_bt)
        lay_ps_comm.addWidget(self.setsofbmode_ps_bt)
        lay_ps_comm.addWidget(self.setslowref_ps_bt)
        lay_ps_comm.addWidget(self.currzero_ps_bt1)
        lay_ps_comm.addWidget(self.reset_ps_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(self.prep_sidclink_bt)
        lay_ps_comm.addWidget(self.init_sips_bt)
        lay_ps_comm.addWidget(self.aux_label)
        lay_ps_comm.addWidget(
            QLabel('<h4>Config DCLinks</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.turnon_dcl_bt)
        lay_ps_comm.addWidget(self.checkctrlloop_dcl_bt)
        lay_ps_comm.addWidget(self.setvolt_dcl_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Test</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.turnon_ps_bt)
        lay_ps_comm.addWidget(self.setcheckctrlloop_ps_bt)
        lay_ps_comm.addWidget(self.test_ps_bt)
        lay_ps_comm.addWidget(self.currzero_ps_bt2)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps_comm.addWidget(
            QLabel('<h4>Restore</h4>', self, alignment=Qt.AlignCenter))
        lay_ps_comm.addWidget(self.restoretrigger_ps_bt)
        lay_ps_comm.addWidget(QLabel(''))
        lay_ps.addWidget(gbox_ps_comm, 0, 1)
        lay_ps.setColumnStretch(1, 1)

        self.tab.addTab(self.ps_wid, 'PS')

        # # PU
        self.pu_wid = QWidget(self)
        lay_pu = QGridLayout(self.pu_wid)
        lay_pu.setContentsMargins(0, 9, 0, 0)
        lay_pu.setHorizontalSpacing(0)

        # PU selection
        self.pu_tree = PVNameTree(items=self._get_pu_tree_names(),
                                  tree_levels=('sec', ),
                                  parent=self)
        self.pu_tree.tree.setHeaderHidden(True)
        self.pu_tree.setSizePolicy(QSizePolicy.Preferred,
                                   QSizePolicy.Preferred)
        self.pu_tree.tree.setColumnCount(1)
        self.pu_tree.tree.doubleClicked.connect(self._open_detail)

        gbox_pu_select = QGroupBox('Select PU: ', self)
        gbox_pu_select.setObjectName('select')
        gbox_pu_select.setStyleSheet("""
            #select{
                border-top: 0px solid transparent;
                border-left: 0px solid transparent;
                border-bottom: 0px solid transparent;
            }""")
        lay_pu_select = QVBoxLayout(gbox_pu_select)
        lay_pu_select.addWidget(self.pu_tree)
        lay_pu.addWidget(gbox_pu_select, 0, 0)
        lay_pu.setColumnStretch(0, 1)

        # PU commands
        self.checkstatus_pu_bt = QPushButton('Show Status Summary', self)
        self.checkstatus_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.checkstatus_pu_bt.clicked.connect(_part(self._check_status, 'PU'))
        self.checkstatus_pu_bt.setToolTip(
            'Check PU interlock status and, if powered on, '
            'check if it is following voltage setpoint')

        self.voltzero_pu_bt1 = QPushButton('Set PU Voltage to zero', self)
        self.voltzero_pu_bt1.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.voltzero_pu_bt1.clicked.connect(_part(self._set_zero_pu, False))

        self.reset_pu_bt = QPushButton('Reset PU', self)
        self.reset_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.reset_pu_bt.clicked.connect(_part(self._reset_intlk, 'PU'))

        self.turnon_pu_bt = QPushButton('Turn PU On', self)
        self.turnon_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.turnon_pu_bt.clicked.connect(
            _part(self._set_check_pwrstate, 'PU', 'on', True))

        self.enblpulse_pu_bt = QPushButton('Enable PU Pulse', self)
        self.enblpulse_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.enblpulse_pu_bt.clicked.connect(_part(self._set_check_pulse,
                                                   'on'))

        self.enbltrigger_pu_bt = QPushButton('Enable PU triggers', self)
        self.enbltrigger_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.enbltrigger_pu_bt.clicked.connect(
            _part(self._set_check_trigger_state, 'PU', 'on'))

        self.test_pu_bt = QPushButton('Set PU Voltage to test value', self)
        self.test_pu_bt.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.test_pu_bt.clicked.connect(self._set_test_pu)

        self.voltzero_pu_bt2 = QPushButton('Set PU Voltage to zero', self)
        self.voltzero_pu_bt2.clicked.connect(_part(self._set_lastcomm, 'PU'))
        self.voltzero_pu_bt2.clicked.connect(_part(self._set_zero_pu, True))

        self.restoretrigger_pu_bt = QPushButton('Restore PU triggers', self)
        self.restoretrigger_pu_bt.clicked.connect(
            _part(self._set_lastcomm, 'PU'))
        self.restoretrigger_pu_bt.clicked.connect(
            _part(self._restore_triggers_state, 'PU'))

        gbox_pu_comm = QGroupBox('Commands', self)
        gbox_pu_comm.setObjectName('comm')
        gbox_pu_comm.setStyleSheet('#comm{border: 0px solid transparent;}')
        lay_pu_comm = QVBoxLayout(gbox_pu_comm)
        lay_pu_comm.setContentsMargins(20, 9, 20, 9)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Check</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.checkstatus_pu_bt)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Prepare</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.voltzero_pu_bt1)
        lay_pu_comm.addWidget(self.reset_pu_bt)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Test</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.turnon_pu_bt)
        lay_pu_comm.addWidget(self.enblpulse_pu_bt)
        lay_pu_comm.addWidget(self.enbltrigger_pu_bt)
        lay_pu_comm.addWidget(self.test_pu_bt)
        lay_pu_comm.addWidget(self.voltzero_pu_bt2)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu_comm.addWidget(
            QLabel('<h4>Restore</h4>', self, alignment=Qt.AlignCenter))
        lay_pu_comm.addWidget(self.restoretrigger_pu_bt)
        lay_pu_comm.addWidget(QLabel(''))
        lay_pu.addWidget(gbox_pu_comm, 0, 1)
        lay_pu.setColumnStretch(1, 1)

        self.tab.addTab(self.pu_wid, 'PU')

        # lists
        self.label_lastcomm = QLabel('Last Command: ', self)
        self.ok_ps = QListWidget(self)
        self.ok_ps.setObjectName('OkList')
        self.ok_ps.doubleClicked.connect(self._open_detail)
        self.ok_ps.setSelectionMode(QAbstractItemView.MultiSelection)
        self.ok_ps.setToolTip(
            'Select rows and press Ctrl+C to copy and Esc to deselect.')
        self.nok_ps = QListWidget(self)
        self.nok_ps.setObjectName('NokList')
        self.nok_ps.doubleClicked.connect(self._open_detail)
        self.nok_ps.setSelectionMode(QAbstractItemView.MultiSelection)
        self.nok_ps.setToolTip(
            'Select rows and press Ctrl+C to copy and Esc to deselect.')
        self.clearlists_bt = QPushButton('Clear', self)
        self.clearlists_bt.clicked.connect(self._clear_lastcomm)
        self.ok_ps_aux_list = list()
        self.nok_ps_aux_list = list()
        hbox = QHBoxLayout()
        hbox.addWidget(self.label_lastcomm)
        hbox.addWidget(self.clearlists_bt, alignment=Qt.AlignRight)
        list_layout = QGridLayout()
        list_layout.setContentsMargins(0, 0, 0, 0)
        list_layout.setVerticalSpacing(6)
        list_layout.setHorizontalSpacing(9)
        list_layout.addLayout(hbox, 0, 0, 1, 2)
        list_layout.addWidget(
            QLabel('<h4>Ok</h4>', self, alignment=Qt.AlignCenter), 1, 0)
        list_layout.addWidget(self.ok_ps, 2, 0)
        list_layout.addWidget(
            QLabel('<h4>Failed</h4>', self, alignment=Qt.AlignCenter), 1, 1)
        list_layout.addWidget(self.nok_ps, 2, 1)

        # menu
        self.menu = self.menuBar()

        self.act_cycle = self.menu.addAction('Open Cycle Window')
        connect_newprocess(self.act_cycle,
                           'sirius-hla-as-ps-cycle.py',
                           parent=self)

        self.aux_comm = self.menu.addMenu('Auxiliary commands')

        # # auxiliary PS
        self.ps_menu = self.aux_comm.addMenu('PS')

        self.act_turnoff_ps = self.ps_menu.addAction('Turn PS Off')
        self.act_turnoff_ps.triggered.connect(_part(self._set_lastcomm, 'PS'))
        self.act_turnoff_ps.triggered.connect(
            _part(self._set_check_pwrstate, 'PS', 'off', True))

        self.act_turnoff_dclink = self.ps_menu.addAction('Turn DCLinks Off')
        self.act_turnoff_dclink.triggered.connect(
            _part(self._set_lastcomm, 'PS'))
        self.act_turnoff_dclink.triggered.connect(
            _part(self._set_check_pwrstate_dclinks, 'off'))

        self.act_setcurrent_ps = self.ps_menu.addAction('Set PS Current')
        self.act_setcurrent_ps.triggered.connect(
            _part(self._set_lastcomm, 'PS'))
        self.act_setcurrent_ps.triggered.connect(self._set_check_current)

        self.act_opmode_ps = self.ps_menu.addAction('Set PS OpMode')
        self.act_opmode_ps.triggered.connect(_part(self._set_lastcomm, 'PS'))
        self.act_opmode_ps.triggered.connect(self._set_check_opmode)

        # # auxiliary PU
        self.pu_menu = self.aux_comm.addMenu('PU')

        self.act_turnoff_pu = self.pu_menu.addAction('Turn PU Off')
        self.act_turnoff_pu.triggered.connect(_part(self._set_lastcomm, 'PU'))
        self.act_turnoff_pu.triggered.connect(
            _part(self._set_check_pwrstate, 'PU', 'off', True))

        self.act_dsblpulse_pu = self.pu_menu.addAction('Disable PU Pulse')
        self.act_dsblpulse_pu.triggered.connect(_part(self._set_lastcomm,
                                                      'PU'))
        self.act_dsblpulse_pu.triggered.connect(
            _part(self._set_check_pulse, 'off'))

        # layout
        lay = QGridLayout()
        lay.setHorizontalSpacing(15)
        lay.setVerticalSpacing(5)
        lay.addWidget(
            QLabel('<h3>Power Supplies Test</h3>',
                   self,
                   alignment=Qt.AlignCenter), 0, 0, 1, 3)
        lay.addWidget(self.tab, 1, 0)
        lay.addLayout(list_layout, 1, 1)
        lay.setColumnStretch(0, 2)
        lay.setColumnStretch(1, 2)
        lay.setRowStretch(0, 2)
        lay.setRowStretch(1, 18)
        self.central_widget.setLayout(lay)

    # ---------- commands ------------

    def _check_comm(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return
        dclinks = self._get_related_dclinks(devices, include_regatrons=True)
        devices.extend(dclinks)

        task0 = CreateTesters(devices, parent=self)
        task1 = CheckComm(devices, parent=self)
        task1.itemDone.connect(self._log)

        labels = [
            'Connecting to devices...',
            'Checking PS and DCLinks Comm. Status...'
        ]
        tasks = [task0, task1]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _check_status(self, dev_type):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
            if not devices:
                return
            dclinks = self._get_related_dclinks(devices,
                                                include_regatrons=True)
            devices.extend(dclinks)
            label1 = 'Reading PS and DCLinks Status...'
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
            if not devices:
                return
            label1 = 'Reading PU Status...'

        task0 = CreateTesters(devices, parent=self)
        task1 = CheckStatus(devices, parent=self)
        task1.itemDone.connect(self._log)

        labels = ['Connecting to devices...', label1]
        tasks = [task0, task1]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_trigger_state(self, dev_type, state):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
        if not devices:
            return

        task1 = SetTriggerState(parent=self,
                                dis=dev_type,
                                state=state,
                                devices=devices)
        task2 = CheckTriggerState(parent=self,
                                  dis=dev_type,
                                  state=state,
                                  devices=devices)
        task2.itemDone.connect(self._log)
        tasks = [task1, task2]

        labels = [
            'Disabling ' + dev_type + ' triggers...',
            'Checking ' + dev_type + ' trigger states...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_opmode_slowref(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return
        dclinks = self._get_related_dclinks(devices)
        devices.extend(dclinks)
        devices = [dev for dev in devices if 'LI-' not in dev]

        task0 = CreateTesters(devices, parent=self)
        task1 = SetOpMode(devices, state=_PSC.OpMode.SlowRef, parent=self)
        task2 = CheckOpMode(devices,
                            state=[
                                _PSC.States.SlowRef, _PSC.States.Off,
                                _PSC.States.Interlock
                            ],
                            parent=self)
        task2.itemDone.connect(self._log)

        labels = [
            'Connecting to devices...', 'Setting PS OpMode to SlowRef...',
            'Checking PS OpMode...'
        ]
        tasks = [task0, task1, task2]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _reset_intlk(self, dev_type):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
            if not devices:
                return
            dclinks = self._get_related_dclinks(devices,
                                                include_regatrons=True)
            devices.extend(dclinks)
            dev_label = 'PS and DCLinks'
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
            if not devices:
                return
            dev_label = 'PU'
        devices_not_rst = {
            dev
            for dev in devices
            if dev.sec != 'LI' and dev.dev not in ('FCH', 'FCV')
        }

        task0 = CreateTesters(devices, parent=self)
        task1 = ResetIntlk(devices_not_rst, parent=self)
        task2 = CheckIntlk(devices, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Reseting ' + dev_label + '...',
            'Checking ' + dev_label + ' Interlocks...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_pwrstate(self, dev_type, state, show=True):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if isinstance(dev_type, list):
            devices = list(dev_type)
            dev_type = devices[0].dis
        else:
            if dev_type == 'PS':
                devices = self._get_selected_ps()
            elif dev_type == 'PU':
                devices = self._get_selected_pu()
        if not devices:
            return

        if state == 'on' and dev_type == 'PS':
            dev2ctrl = list(set(devices) - set(self._si_fam_psnames))
        else:
            dev2ctrl = devices

        task0 = CreateTesters(devices, parent=self)
        task1 = SetPwrState(dev2ctrl, state=state, parent=self)
        task2 = CheckPwrState(devices, state=state, is_test=True, parent=self)
        task2.itemDone.connect(_part(self._log, show=show))
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...',
            'Turning ' + dev_type + ' ' + state + '...',
            'Checking ' + dev_type + ' powered ' + state + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _prepare_sidclinks(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        selected = self._get_selected_ps()
        ps2check = [ps for ps in selected if ps in self._si_fam_psnames]
        dclinks = self._get_related_dclinks(ps2check, include_regatrons=True)
        if not ps2check:
            return

        # if power state is on, do nothing
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(ps2check, state='on', is_test=False, show=False)
        if set(self.ok_ps_aux_list) == set(ps2check):
            for dev in dclinks:
                self._log(dev, True)
            return

        ps2act = list(self.nok_ps_aux_list)  # act in PS off
        dcl2act = self._get_related_dclinks(ps2act, include_regatrons=True)
        # print('act', ps2act, dcl2act)

        # if need initializing, check if DCLinks are turned off
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(dcl2act, state='off', is_test=False, show=False)
        if not self.nok_ps_aux_list:
            for dev in dclinks:
                self._log(dev, True)
            return

        dcl2ctrl = list(self.nok_ps_aux_list)  # control DCLink on
        dcl_ok = set(dclinks) - set(dcl2ctrl)
        ps2ctrl = set()  # get related psnames
        for dcl in dcl2ctrl:
            pss = PSSearch.conv_dclink_2_psname(dcl)
            ps2ctrl.update(pss)
        # print('ctrl', ps2ctrl, dcl2ctrl)

        # if some DCLink is on, make sure related PS are turned off
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(ps2ctrl, state='off', is_test=False, show=False)
        if self.nok_ps_aux_list:
            ps2ctrl = list(self.nok_ps_aux_list)

            self.ok_ps_aux_list.clear()
            self.nok_ps_aux_list.clear()
            self._set_zero_ps(ps2ctrl, show=False)

            self.ok_ps_aux_list.clear()
            self.nok_ps_aux_list.clear()
            self._set_check_pwrstate(dev_type=ps2ctrl, state='off', show=False)

            if self.nok_ps_aux_list:
                for dev in self.ok_ps_aux_list:
                    self._log(dev, True)
                for dev in self.nok_ps_aux_list:
                    self._log(dev, False)
                text = 'The listed PS seems to be taking too\n'\
                    'long to turn off.\n'\
                    'Try to execute this step once again.'
                QMessageBox.warning(self, 'Message', text)
                return

        # finally, turn DCLinks off
        self._set_check_pwrstate_dclinks(state='off',
                                         devices=dcl2ctrl,
                                         ps2check=ps2ctrl)
        # log DCLinks Ok
        for dev in dcl_ok:
            self._log(dev, True)

    def _set_check_pwrstateinit(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        selected = self._get_selected_ps()
        devices = [ps for ps in selected if ps in self._si_fam_psnames]
        if not devices:
            return

        # if power state is on, do nothing
        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(devices, state='on', is_test=False, show=False)
        if len(self.ok_ps_aux_list) == len(devices):
            for dev in self.ok_ps_aux_list:
                self._log(dev, True)
            return

        # if need initializing, check if DCLinks are turned off before continue
        ps_ok = list(self.ok_ps_aux_list)
        ps2ctrl = list(self.nok_ps_aux_list)  # check PS off
        dcl2check = self._get_related_dclinks(ps2ctrl, include_regatrons=True)
        # print('set_check_pwrstateinit', ps2ctrl)

        self.ok_ps_aux_list.clear()
        self.nok_ps_aux_list.clear()
        self._check_pwrstate(dcl2check, state='off', is_test=False, show=False)
        if len(self.nok_ps_aux_list) > 0:
            for dev in self.ok_ps_aux_list:
                self._log(dev, True)
            for dev in self.nok_ps_aux_list:
                self._log(dev, False)
            QMessageBox.critical(
                self, 'Message', 'Make sure related DCLinks are turned\n'
                'off before initialize SI Fam PS!')
            return

        # list in Ok PS already on
        for dev in ps_ok:
            self._log(dev, True)

        # then, initialize SI Fam PS
        task0 = CreateTesters(ps2ctrl, parent=self)
        task1 = SetPwrState(ps2ctrl, state='on', parent=self)
        task2 = CheckOpMode(ps2ctrl,
                            state=_PSC.States.Initializing,
                            parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Initializing SI Fam PS...',
            'Checking SI Fam PS initialized...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_pulse(self, state):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_pu()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetPulse(devices, state=state, parent=self)
        task2 = CheckPulse(devices, state=state, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Turning PU Pulse ' + state + '...',
            'Checking PU Pulse ' + state + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_pwrstate_dclinks(self,
                                    state,
                                    devices=list(),
                                    ps2check=list()):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if not devices:
            pwrsupplies = self._get_selected_ps()
            if not pwrsupplies:
                return
            devices = self._get_related_dclinks(pwrsupplies,
                                                include_regatrons=True)
            ps2check = set()
            for dev in devices:
                ps2check.update(PSSearch.conv_dclink_2_psname(dev))
        if not devices:
            return

        if state == 'off':
            self.ok_ps_aux_list.clear()
            self.nok_ps_aux_list.clear()
            self._check_pwrstate(ps2check, state='offintlk', show=False)
            if len(self.nok_ps_aux_list) > 0:
                for dev in self.ok_ps_aux_list:
                    self._log(dev, True)
                for dev in self.nok_ps_aux_list:
                    self._log(dev, False)
                QMessageBox.critical(
                    self, 'Message', 'Make sure all related power supplies\n'
                    'are turned off before turning DCLinks off!')
                return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetPwrState(devices, state=state, parent=self)
        task2 = CheckPwrState(devices, state=state, is_test=True, parent=self)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to DCLinks...', 'Turning DCLinks ' + state + '...',
            'Checking DCLinks powered ' + state + '...'
        ]

        if state == 'on':
            task3 = CheckInitOk(devices, parent=self)
            task3.itemDone.connect(self._log)
            tasks.append(task3)
            labels.append('Wait DCLinks initialize...')
        else:
            task2.itemDone.connect(self._log)

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _check_pwrstate(self, devices, state, is_test=True, show=True):
        self.ok_ps.clear()
        self.nok_ps.clear()

        task0 = CreateTesters(devices, parent=self)

        if state == 'offintlk':
            text = 'off or interlock'
            task1 = CheckOpMode(devices,
                                state=[_PSC.States.Off, _PSC.States.Interlock],
                                parent=self)
        else:
            text = state
            task1 = CheckPwrState(devices,
                                  state=state,
                                  is_test=is_test,
                                  parent=self)
        task1.itemDone.connect(_part(self._log, show=show))
        tasks = [task0, task1]

        labels = [
            'Connecting to devices...',
            'Checking devices powered ' + text + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_ctrlloop(self, dev_type='pwrsupply'):
        self.ok_ps.clear()
        self.nok_ps.clear()
        pwrsupplies = self._get_selected_ps()
        if not pwrsupplies:
            return
        devices_set = {}
        if dev_type == 'pwrsupply':
            devices = {dev for dev in pwrsupplies if 'LI' not in dev}
            devices_set = {dev for dev in devices if dev.dev in ('FCH', 'FCV')}
        else:
            devices = self._get_related_dclinks(pwrsupplies)
        if not devices:
            return

        tasks = [
            CreateTesters(devices, parent=self),
        ]
        labels = [
            'Connecting to devices...',
        ]
        if devices_set:
            task1 = SetCtrlLoop(devices_set, parent=self)
            task2 = CheckCtrlLoop(devices, parent=self)
            task2.itemDone.connect(self._log)
            tasks.extend([task1, task2])
            labels.extend(
                ['Setting CtrlLoop...', 'Checking CtrlLoop state...'])
        else:
            task1 = CheckCtrlLoop(devices, parent=self)
            task1.itemDone.connect(self._log)
            tasks.append(task1)
            labels.append('Checking CtrlLoop state...')
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_dclinks_capvolt(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        pwrsupplies = self._get_selected_ps()
        if not pwrsupplies:
            return
        devices = self._get_related_dclinks(pwrsupplies,
                                            include_regatrons=True)
        dev_exc_regatrons = {
            dev
            for dev in devices
            if PSSearch.conv_psname_2_psmodel(dev) != 'REGATRON_DCLink'
        }
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCapBankVolt(dev_exc_regatrons, parent=self)
        task2 = CheckCapBankVolt(devices, parent=self)
        task2.itemDone.connect(self._log)
        labels = [
            'Connecting to devices...', 'Setting capacitor bank voltage...',
            'Checking capacitor bank voltage...'
        ]
        tasks = [task0, task1, task2]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_fbp_sofbmode(self, state):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        devices = [
            dev for dev in devices
            if PSSearch.conv_psname_2_psmodel(dev) == 'FBP'
        ]
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetSOFBMode(devices, state=state, parent=self)
        task2 = CheckSOFBMode(devices, state=state, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Turning PS SOFBMode ' + state + '...',
            'Checking PS SOFBMode ' + state + '...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_test_ps(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCurrent(devices, is_test=True, parent=self)
        task2 = CheckCurrent(devices, is_test=True, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Testing PS... Setting current...',
            'Testing PS... Checking current value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_zero_ps(self, devices=list(), show=True):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if not devices:
            devices = self._get_selected_ps()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCurrent(devices, parent=self)
        task2 = CheckCurrent(devices, parent=self)
        task2.itemDone.connect(_part(self._log, show=show))
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Setting current to zero...',
            'Checking current value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_current(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        if not devices:
            return

        value, ok = QInputDialog.getDouble(self, "Setpoint Input",
                                           "Insert current setpoint: ")
        if not ok:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetCurrent(devices, value=value, parent=self)
        task2 = CheckCurrent(devices, value=value, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Setting current...',
            'Checking current value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_check_opmode(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_ps()
        devices = [
            dev for dev in devices
            if dev.sec != 'LI' and dev.dev not in ('FCH', 'FCV')
        ]
        if not devices:
            return

        state, ok = QInputDialog.getItem(self,
                                         "OpMode Input",
                                         "Select OpMode: ",
                                         _PSE.OPMODES,
                                         editable=False)
        if not ok:
            return
        state2set = getattr(_PSC.OpMode, state)
        state2check = getattr(_PSC.States, state)

        task0 = CreateTesters(devices, parent=self)
        task1 = SetOpMode(devices, state=state2set, parent=self)
        task2 = CheckOpMode(devices, state=state2check, parent=self)
        task2.itemDone.connect(self._log)

        labels = [
            'Connecting to devices...',
            'Setting PS OpMode to ' + state + '...',
            'Checking PS OpMode in ' + state + '...'
        ]
        tasks = [task0, task1, task2]
        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_test_pu(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_pu()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetVoltage(devices, is_test=True, parent=self)
        task2 = CheckVoltage(devices, is_test=True, parent=self)
        task2.itemDone.connect(self._log)
        tasks = [task0, task1, task2]

        labels = [
            'Connecting to devices...', 'Testing PU... Setting voltage...',
            'Testing PU... Checking voltage value...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _set_zero_pu(self, check=True):
        self.ok_ps.clear()
        self.nok_ps.clear()
        devices = self._get_selected_pu()
        if not devices:
            return

        task0 = CreateTesters(devices, parent=self)
        task1 = SetVoltage(devices, parent=self)
        tasks = [task0, task1]

        labels = ['Connecting to devices...', 'Setting voltage to zero...']

        if check:
            task2 = CheckVoltage(devices, parent=self)
            task2.itemDone.connect(self._log)
            tasks.append(task2)
            labels.append('Checking voltage value...')
        else:
            task1.itemDone.connect(self._log)

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    def _restore_triggers_state(self, dev_type):
        self.ok_ps.clear()
        self.nok_ps.clear()
        if dev_type == 'PS':
            devices = self._get_selected_ps()
        elif dev_type == 'PU':
            devices = self._get_selected_pu()
        if not devices:
            return

        task1 = SetTriggerState(parent=self,
                                restore_initial_value=True,
                                dis=dev_type,
                                devices=devices)
        task2 = CheckTriggerState(parent=self,
                                  restore_initial_value=True,
                                  dis=dev_type,
                                  devices=devices)
        task2.itemDone.connect(self._log)
        tasks = [task1, task2]

        labels = [
            'Restoring ' + dev_type + ' trigger states...',
            'Checking ' + dev_type + ' trigger states...'
        ]

        dlg = ProgressDialog(labels, tasks, self)
        dlg.exec_()

    # ---------- log updates -----------

    def _set_lastcomm(self, dev_type):
        sender_text = self.sender().text()
        self.label_lastcomm.setText('Last Command: ' + dev_type + ' - ' +
                                    sender_text)

    def _clear_lastcomm(self):
        self.ok_ps.clear()
        self.nok_ps.clear()
        self.label_lastcomm.setText('Last Command: ')

    def _log(self, name, status, show=True):
        if show:
            if status:
                self.ok_ps.addItem(name)
            else:
                self.nok_ps.addItem(name)
        else:
            if status:
                self.ok_ps_aux_list.append(name)
            else:
                self.nok_ps_aux_list.append(name)

    # ---------- devices control ----------

    def _get_ps_tree_names(self):
        # add LI, TB, BO, TS
        psnames = PSSearch.get_psnames({'sec': '(LI|TB|BO|TS)', 'dis': 'PS'})
        # add SI Fams
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'sub': 'Fam',
                'dis': 'PS',
                'dev': '(B.*|Q.*|S.*)'
            }))
        # add SI Corrs
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'sub': '[0-2][0-9].*',
                'dis': 'PS',
                'dev': '(CH|CV)'
            }))
        # add SI QTrims
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'sub': '[0-2][0-9].*',
                'dis': 'PS',
                'dev': '(QD.*|QF.*|Q[1-4])'
            }))
        # add SI QSkews
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'dis': 'PS',
                'dev': 'QS'
            }))
        # add SI Fast Corrs
        psnames.extend(
            PSSearch.get_psnames({
                'sec': 'SI',
                'dis': 'PS',
                'dev': 'FC.*'
            }))
        return psnames

    def _get_pu_tree_names(self):
        punames = PSSearch.get_psnames({
            'sec': '(TB|BO|TS|SI)',
            'dis': 'PU',
            'dev': '.*(Kckr|Sept).*'
        })
        return punames

    def _get_selected_ps(self):
        devices = self.ps_tree.checked_items()
        if not devices:
            QMessageBox.critical(self, 'Message', 'No power supply selected!')
            return False
        devices = [PVName(dev) for dev in devices]
        return devices

    def _get_selected_pu(self):
        devices = self.pu_tree.checked_items()
        if not devices:
            QMessageBox.critical(self, 'Message',
                                 'No pulsed power supply selected!')
            return False
        devices = [PVName(dev) for dev in devices]
        return devices

    def _get_related_dclinks(self, psnames, include_regatrons=False):
        if isinstance(psnames, str):
            psnames = [
                psnames,
            ]
        alldclinks = set()
        for name in psnames:
            if 'LI' in name:
                continue
            dclinks = PSSearch.conv_psname_2_dclink(name)
            if dclinks:
                dclink_model = PSSearch.conv_psname_2_psmodel(dclinks[0])
                if dclink_model != 'REGATRON_DCLink':
                    alldclinks.update(dclinks)
                elif include_regatrons:
                    for dcl in dclinks:
                        dcl_typ = PSSearch.conv_psname_2_pstype(dcl)
                        if dcl_typ == 'as-dclink-regatron-master':
                            alldclinks.add(dcl)
        alldclinks = [PVName(dev) for dev in alldclinks]
        return alldclinks

    def _open_detail(self, index):
        name = PVName(index.data())
        if name.dis == 'TI':
            app = QApplication.instance()
            wind = create_window_from_widget(HLTriggerDetailed,
                                             title=name,
                                             is_main=True)
            app.open_window(wind, parent=self, **{'device': name})
            return
        elif not _re.match('.*-.*:.*-.*', name):
            if index.model().rowCount(index) == 1:
                name = PVName(index.child(0, 0).data())
            else:
                return
        if name.dis == 'PS':
            run_newprocess(['sirius-hla-as-ps-detail.py', name])
        elif name.dis == 'PU':
            run_newprocess(['sirius-hla-as-pu-detail.py', name])

    # ---------- update setup ----------

    def _handle_checked_items_changed(self, item):
        devname = PVName(item.data(0, Qt.DisplayRole))
        if not _re.match('.*-.*:.*-.*', devname):
            return
        state2set = item.checkState(0)

        # ensure power supplies that share dclinks are checked together
        if devname in self._si_fam_psnames and not self._is_adv_mode:
            dclinks = PSSearch.conv_psname_2_dclink(devname)
            if dclinks:
                psname2check = set()
                for dcl in dclinks:
                    relps = PSSearch.conv_dclink_2_psname(dcl)
                    relps.remove(devname)
                    psname2check.update(relps)
                self.ps_tree.tree.blockSignals(True)
                for psn in psname2check:
                    item2check = self.ps_tree._item_map[psn]
                    if item2check.checkState(0) != state2set:
                        item2check.setData(0, Qt.CheckStateRole, state2set)
                self.ps_tree.tree.blockSignals(False)

        self._needs_update_setup = True

    def _update_setup(self):
        if not self._needs_update_setup:
            return
        self._needs_update_setup = False

        # show/hide buttons to initialize SI Fam PS
        has_sifam = False
        for psn in self._si_fam_psnames:
            item = self.ps_tree._item_map[psn]
            has_sifam |= item.checkState(0) != 0

        self.prep_sidclink_bt.setVisible(has_sifam)
        self.init_sips_bt.setVisible(has_sifam)
        self.aux_label.setVisible(has_sifam)

        # set CtrlLoop button label
        has_fast = False
        for psn in self._si_fastcorrs:
            item = self.ps_tree._item_map[psn]
            has_fast |= item.checkState(0) != 0

        text = 'Set(FC) and Check PS CtrlLoop' \
            if has_fast else 'Check PS CtrlLoop'
        self.setcheckctrlloop_ps_bt.setText(text)

    # ---------- events ----------

    def keyPressEvent(self, evt):
        """Implement keyPressEvent."""
        if evt.matches(QKeySequence.Copy):
            if self.ok_ps.underMouse():
                items = self.ok_ps.selectedItems()
            elif self.nok_ps.underMouse():
                items = self.nok_ps.selectedItems()
            items = '\n'.join([i.text() for i in items])
            QApplication.clipboard().setText(items)
        if evt.key() == Qt.Key_Escape:
            if self.ok_ps.underMouse():
                items = self.ok_ps.clearSelection()
            elif self.nok_ps.underMouse():
                items = self.nok_ps.clearSelection()
        super().keyPressEvent(evt)
コード例 #31
0
ファイル: fileswitcher.py プロジェクト: ethan-lau/spyder
class FileSwitcher(QDialog):
    """A Sublime-like file switcher."""
    sig_goto_file = Signal(int)
    sig_close_file = Signal(int)

    # Constants that define the mode in which the list widget is working
    # FILE_MODE is for a list of files, SYMBOL_MODE if for a list of symbols
    # in a given file when using the '@' symbol.
    FILE_MODE, SYMBOL_MODE = [1, 2]

    def __init__(self, parent, tabs, data):
        QDialog.__init__(self, parent)

        # Variables
        self.tabs = tabs                  # Editor stack tabs
        self.data = data                  # Editor data
        self.mode = self.FILE_MODE        # By default start in this mode
        self.initial_cursors = None       # {fullpath: QCursor}
        self.initial_path = None          # Fullpath of initial active editor
        self.initial_editor = None        # Initial active editor
        self.line_number = None           # Selected line number in filer
        self.is_visible = False           # Is the switcher visible?

        help_text = _("Press <b>Enter</b> to switch files or <b>Esc</b> to "
                      "cancel.<br><br>Type to filter filenames.<br><br>"
                      "Use <b>:number</b> to go to a line, e.g. "
                      "<b><code>main:42</code></b><br>"
                      "Use <b>@symbol_text</b> to go to a symbol, e.g. "
                      "<b><code>@init</code></b>"
                      "<br><br> Press <b>Ctrl+W</b> to close current tab.<br>")

        # Either allow searching for a line number or a symbol but not both
        regex = QRegExp("([A-Za-z0-9_]{0,100}@[A-Za-z0-9_]{0,100})|" +
                        "([A-Za-z]{0,100}:{0,1}[0-9]{0,100})")

        # Widgets
        self.edit = QLineEdit(self)
        self.help = HelperToolButton()
        self.list = QListWidget(self)
        self.filter = KeyPressFilter()
        regex_validator = QRegExpValidator(regex, self.edit)

        # Widgets setup
        self.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint)
        self.setWindowOpacity(0.95)
        self.edit.installEventFilter(self.filter)
        self.edit.setValidator(regex_validator)
        self.help.setToolTip(help_text)
        self.list.setItemDelegate(HTMLDelegate(self))

        # Layout
        edit_layout = QHBoxLayout()
        edit_layout.addWidget(self.edit)
        edit_layout.addWidget(self.help)
        layout = QVBoxLayout()
        layout.addLayout(edit_layout)
        layout.addWidget(self.list)
        self.setLayout(layout)

        # Signals
        self.rejected.connect(self.restore_initial_state)
        self.filter.sig_up_key_pressed.connect(self.previous_row)
        self.filter.sig_down_key_pressed.connect(self.next_row)
        self.edit.returnPressed.connect(self.accept)
        self.edit.textChanged.connect(self.setup)
        self.list.itemSelectionChanged.connect(self.item_selection_changed)
        self.list.clicked.connect(self.edit.setFocus)

        # Setup
        self.save_initial_state()
        self.set_dialog_position()
        self.setup()

    # --- Properties
    @property
    def editors(self):
        return [self.tabs.widget(index) for index in range(self.tabs.count())]

    @property
    def line_count(self):
        return [editor.get_line_count() for editor in self.editors]

    @property
    def save_status(self):
        return [getattr(td, 'newly_created', False) for td in self.data]

    @property
    def paths(self):
        return [getattr(td, 'filename', None) for td in self.data]

    @property
    def filenames(self):
        return [self.tabs.tabText(index) for index in range(self.tabs.count())]

    @property
    def current_path(self):
        return self.paths_by_editor[self.get_editor()]

    @property
    def paths_by_editor(self):
        return dict(zip(self.editors, self.paths))

    @property
    def editors_by_path(self):
        return dict(zip(self.paths, self.editors))

    @property
    def filter_text(self):
        """Get the normalized (lowecase) content of the filter text."""
        return to_text_string(self.edit.text()).lower()

    def save_initial_state(self):
        """Saves initial cursors and initial active editor."""
        paths = self.paths
        self.initial_editor = self.get_editor()
        self.initial_cursors = {}

        for i, editor in enumerate(self.editors):
            if editor is self.initial_editor:
                self.initial_path = paths[i]
            self.initial_cursors[paths[i]] = editor.textCursor()

    def accept(self):
        self.is_visible = False
        QDialog.accept(self)
        self.list.clear()

    def restore_initial_state(self):
        """Restores initial cursors and initial active editor."""
        self.list.clear()
        self.is_visible = False
        editors = self.editors_by_path

        for path in self.initial_cursors:
            cursor = self.initial_cursors[path]
            if path in editors:
                self.set_editor_cursor(editors[path], cursor)

        if self.initial_editor in self.paths_by_editor:
            index = self.paths.index(self.initial_path)
            self.sig_goto_file.emit(index)

    def set_dialog_position(self):
        """Positions the file switcher dialog in the center of the editor."""
        parent = self.parent()
        geo = parent.geometry()
        width = self.list.width()  # This has been set in setup

        left = parent.geometry().width()/2 - width/2
        top = 0
        while parent:
            geo = parent.geometry()
            top += geo.top()
            left += geo.left()
            parent = parent.parent()

        # Note: the +1 pixel on the top makes it look better
        self.move(left, top + self.tabs.tabBar().geometry().height() + 1)

    def fix_size(self, content, extra=50):
        """Adjusts the width of the file switcher, based on the content."""
        # Update size of dialog based on longest shortened path
        strings = []
        if content:
            for rich_text in content:
                label = QLabel(rich_text)
                label.setTextFormat(Qt.PlainText)
                strings.append(label.text())
                fm = label.fontMetrics()
            max_width = max([fm.width(s)*1.3 for s in strings])
            self.list.setMinimumWidth(max_width + extra)
            self.set_dialog_position()

    # --- Helper methods: List widget
    def count(self):
        """Gets the item count in the list widget."""
        return self.list.count()

    def current_row(self):
        """Returns the current selected row in the list widget."""
        return self.list.currentRow()

    def set_current_row(self, row):
        """Sets the current selected row in the list widget."""
        return self.list.setCurrentRow(row)

    def select_row(self, steps):
        """Select row in list widget based on a number of steps with direction.

        Steps can be positive (next rows) or negative (previous rows).
        """
        row = self.current_row() + steps
        if 0 <= row < self.count():
            self.set_current_row(row)

    def previous_row(self):
        """Select previous row in list widget."""
        self.select_row(-1)

    def next_row(self):
        """Select next row in list widget."""
        self.select_row(+1)

    # --- Helper methods: Editor
    def get_editor(self, index=None, path=None):
        """Get editor by index or path.
        
        If no path or index specified the current active editor is returned
        """
        if index:
            return self.tabs.widget(index)
        elif path:
            return self.tabs.widget(index)
        else:
            return self.parent().get_current_editor()

    def set_editor_cursor(self, editor, cursor):
        """Set the cursor of an editor."""
        pos = cursor.position()
        anchor = cursor.anchor()

        new_cursor = QTextCursor()
        if pos == anchor:
            new_cursor.movePosition(pos)
        else:
            new_cursor.movePosition(anchor)
            new_cursor.movePosition(pos, QTextCursor.KeepAnchor)
        editor.setTextCursor(cursor)

    def goto_line(self, line_number):
        """Go to specified line number in current active editor."""
        if line_number:
            line_number = int(line_number)
            editor = self.get_editor()
            editor.go_to_line(min(line_number, editor.get_line_count()))

    # --- Helper methods: Outline explorer
    def get_symbol_list(self):
        """Get the object explorer data."""
        return self.get_editor().highlighter.get_outlineexplorer_data()

    # --- Handlers
    def item_selection_changed(self):
        """List widget item selection change handler."""
        row = self.current_row()
        if self.count() and row >= 0:
            if self.mode == self.FILE_MODE:
                try:
                    stack_index = self.paths.index(self.filtered_path[row])
                    self.sig_goto_file.emit(stack_index)
                    self.goto_line(self.line_number)
                    self.edit.setFocus()
                except ValueError:
                    pass
            else:
                line_number = self.filtered_symbol_lines[row]
                self.goto_line(line_number)

    def setup_file_list(self, filter_text, current_path):
        """Setup list widget content for file list display."""
        short_paths = shorten_paths(self.paths, self.save_status)
        paths = self.paths
        results = []
        trying_for_line_number = ':' in filter_text

        # Get optional line number
        if trying_for_line_number:
            filter_text, line_number = filter_text.split(':')
        else:
            line_number = None

        # Get all available filenames and get the scores for "fuzzy" matching
        scores = get_search_scores(filter_text, self.filenames,
                                   template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            if score_value != -1:
                text_item = '<big>' + rich_text + '</big>'
                if trying_for_line_number:
                    text_item += " [{0:} {1:}]".format(self.line_count[index],
                                                       _("lines"))
                text_item += "<br><i>{0:}</i>".format(
                    short_paths[index])

                results.append((score_value, index, text_item))

        # Sort the obtained scores and populate the list widget
        self.filtered_path = []
        for result in sorted(results):
            index = result[1]
            text = result[-1]
            path = paths[index]
            item = QListWidgetItem(self.tabs.tabIcon(index), text)
            item.setToolTip(path)
            item.setSizeHint(QSize(0, 25))
            self.list.addItem(item)
            self.filtered_path.append(path)

        # Move selected item in list accordingly and update list size
        if current_path in self.filtered_path:
            self.set_current_row(self.filtered_path.index(current_path))
        elif self.filtered_path:
            self.set_current_row(0)
        self.fix_size(short_paths)

        # If a line number is searched look for it
        self.line_number = line_number
        self.goto_line(line_number)

    def setup_symbol_list(self, filter_text, current_path):
        """Setup list widget content for symbol list display."""
        # Get optional symbol name
        filter_text, symbol_text = filter_text.split('@')

        # Fetch the Outline explorer data, get the icons and values
        oedata = self.get_symbol_list()
        icons = get_python_symbol_icons(oedata)

        symbol_list = process_python_symbol_data(oedata)
        line_fold_token = [(item[0], item[2], item[3]) for item in symbol_list]
        choices = [item[1] for item in symbol_list]
        scores = get_search_scores(symbol_text, choices, template="<b>{0}</b>")

        # Build the text that will appear on the list widget
        results = []
        lines = []
        self.filtered_symbol_lines = []
        for index, score in enumerate(scores):
            text, rich_text, score_value = score
            line, fold_level, token = line_fold_token[index]
            lines.append(text)
            if score_value != -1:
                results.append((score_value, line, text, rich_text,
                                fold_level, icons[index], token))

        template_1 = '<code>{0}<big>{1} {2}</big></code>'
        template_2 = '<br><code>{0}</code><i>[Line {1}]</i>'

        for (score, line, text, rich_text, fold_level, icon,
             token) in sorted(results):
            fold_space = '&nbsp;'*(fold_level)
            line_number = line + 1
            self.filtered_symbol_lines.append(line_number)
            textline = template_1.format(fold_space, token, rich_text)
            textline += template_2.format(fold_space, line_number)
            item = QListWidgetItem(icon, textline)
            item.setSizeHint(QSize(0, 16))
            self.list.addItem(item)

        # Move selected item in list accordingly
        # NOTE: Doing this is causing two problems:
        # 1. It makes the cursor to auto-jump to the last selected
        #    symbol after opening or closing a different file
        # 2. It moves the cursor to the first symbol by default,
        #    which is very distracting.
        # That's why this line is commented!
        # self.set_current_row(0)

        # Update list size
        self.fix_size(lines, extra=125)

    def setup(self):
        """Setup list widget content."""
        if not self.tabs.count():
            self.close()
            return

        self.list.clear()
        current_path = self.current_path
        filter_text = self.filter_text

        # Get optional line or symbol to define mode and method handler
        trying_for_symbol = ('@' in self.filter_text)

        if trying_for_symbol:
            self.mode = self.SYMBOL_MODE
            self.setup_symbol_list(filter_text, current_path)
        else:
            self.mode = self.FILE_MODE
            self.setup_file_list(filter_text, current_path)
コード例 #32
0
ファイル: pathmanager.py プロジェクト: ubuntu11/spyder
class PathManager(QDialog):
    """Path manager dialog."""
    redirect_stdio = Signal(bool)
    sig_path_changed = Signal(object)

    def __init__(self,
                 parent,
                 path=None,
                 read_only_path=None,
                 not_active_path=None,
                 sync=True):
        """Path manager dialog."""
        super(PathManager, self).__init__(parent)
        assert isinstance(path, (tuple, None))

        self.path = path or ()
        self.read_only_path = read_only_path or ()
        self.not_active_path = not_active_path or ()
        self.last_path = getcwd_or_home()
        self.original_path_dict = None

        # Widgets
        self.add_button = None
        self.remove_button = None
        self.movetop_button = None
        self.moveup_button = None
        self.movedown_button = None
        self.movebottom_button = None
        self.sync_button = None
        self.selection_widgets = []
        self.top_toolbar_widgets = self._setup_top_toolbar()
        self.bottom_toolbar_widgets = self._setup_bottom_toolbar()
        self.listwidget = QListWidget(self)
        self.bbox = QDialogButtonBox(QDialogButtonBox.Ok
                                     | QDialogButtonBox.Cancel)
        self.button_ok = self.bbox.button(QDialogButtonBox.Ok)

        # Widget setup
        # Destroying the C++ object right after closing the dialog box,
        # otherwise it may be garbage-collected in another QThread
        # (e.g. the editor's analysis thread in Spyder), thus leading to
        # a segmentation fault on UNIX or an application crash on Windows
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowTitle(_("PYTHONPATH manager"))
        self.setWindowIcon(ima.icon('pythonpath'))
        self.resize(500, 300)
        self.sync_button.setVisible(os.name == 'nt' and sync)

        # Layouts
        top_layout = QHBoxLayout()
        self._add_widgets_to_layout(self.top_toolbar_widgets, top_layout)

        bottom_layout = QHBoxLayout()
        self._add_widgets_to_layout(self.bottom_toolbar_widgets, bottom_layout)
        bottom_layout.addWidget(self.bbox)

        layout = QVBoxLayout()
        layout.addLayout(top_layout)
        layout.addWidget(self.listwidget)
        layout.addLayout(bottom_layout)
        self.setLayout(layout)

        # Signals
        self.listwidget.currentRowChanged.connect(lambda x: self.refresh())
        self.listwidget.itemChanged.connect(lambda x: self.refresh())
        self.bbox.accepted.connect(self.accept)
        self.bbox.rejected.connect(self.reject)

        # Setup
        self.setup()

    def _add_widgets_to_layout(self, widgets, layout):
        """Helper to add toolbar widgets to top and bottom layout."""
        layout.setAlignment(Qt.AlignLeft)
        for widget in widgets:
            if widget is None:
                layout.addStretch(1)
            else:
                layout.addWidget(widget)

    def _setup_top_toolbar(self):
        """Create top toolbar and actions."""
        self.movetop_button = create_toolbutton(
            self,
            text=_("Move to top"),
            icon=ima.icon('2uparrow'),
            triggered=lambda: self.move_to(absolute=0),
            text_beside_icon=True)
        self.moveup_button = create_toolbutton(
            self,
            text=_("Move up"),
            icon=ima.icon('1uparrow'),
            triggered=lambda: self.move_to(relative=-1),
            text_beside_icon=True)
        self.movedown_button = create_toolbutton(
            self,
            text=_("Move down"),
            icon=ima.icon('1downarrow'),
            triggered=lambda: self.move_to(relative=1),
            text_beside_icon=True)
        self.movebottom_button = create_toolbutton(
            self,
            text=_("Move to bottom"),
            icon=ima.icon('2downarrow'),
            triggered=lambda: self.move_to(absolute=1),
            text_beside_icon=True)

        toolbar = [
            self.movetop_button, self.moveup_button, self.movedown_button,
            self.movebottom_button
        ]
        self.selection_widgets.extend(toolbar)
        return toolbar

    def _setup_bottom_toolbar(self):
        """Create bottom toolbar and actions."""
        self.add_button = create_toolbutton(
            self,
            text=_('Add path'),
            icon=ima.icon('edit_add'),
            triggered=lambda x: self.add_path(),
            text_beside_icon=True)
        self.remove_button = create_toolbutton(
            self,
            text=_('Remove path'),
            icon=ima.icon('edit_remove'),
            triggered=lambda x: self.remove_path(),
            text_beside_icon=True)
        self.sync_button = create_toolbutton(
            self,
            text=_("Synchronize..."),
            icon=ima.icon('fileimport'),
            triggered=self.synchronize,
            tip=_("Synchronize Spyder's path list with PYTHONPATH "
                  "environment variable"),
            text_beside_icon=True)

        self.selection_widgets.append(self.remove_button)
        return [self.add_button, self.remove_button, None, self.sync_button]

    def _create_item(self, path):
        """Helper to create a new list item."""
        item = QListWidgetItem(path)
        item.setIcon(ima.icon('DirClosedIcon'))

        if path in self.read_only_path:
            item.setFlags(Qt.NoItemFlags | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)
        elif path in self.not_active_path:
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Unchecked)
        else:
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            item.setCheckState(Qt.Checked)

        return item

    @property
    def editable_bottom_row(self):
        """Maximum bottom row count that is editable."""
        read_only_count = len(self.read_only_path)
        if read_only_count == 0:
            max_row = self.listwidget.count() - 1
        else:
            max_row = self.listwidget.count() - read_only_count - 1
        return max_row

    def setup(self):
        """Populate list widget."""
        self.listwidget.clear()
        for path in self.path + self.read_only_path:
            item = self._create_item(path)
            self.listwidget.addItem(item)
        self.listwidget.setCurrentRow(0)
        self.original_path_dict = self.get_path_dict()
        self.refresh()

    @Slot()
    def synchronize(self):
        """
        Synchronize Spyder's path list with PYTHONPATH environment variable
        Only apply to: current user, on Windows platforms.
        """
        answer = QMessageBox.question(
            self, _("Synchronize"),
            _("This will synchronize Spyder's path list with "
              "<b>PYTHONPATH</b> environment variable for the current user, "
              "allowing you to run your Python modules outside Spyder "
              "without having to configure sys.path. "
              "<br>"
              "Do you want to clear contents of PYTHONPATH before "
              "adding Spyder's path list?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)

        if answer == QMessageBox.Cancel:
            return
        elif answer == QMessageBox.Yes:
            remove = True
        else:
            remove = False

        from spyder.utils.environ import (get_user_env, listdict2envdict,
                                          set_user_env)
        env = get_user_env()

        # Includes read only paths
        active_path = tuple(k for k, v in self.get_path_dict(True).items()
                            if v)

        if remove:
            ppath = active_path
        else:
            ppath = env.get('PYTHONPATH', [])
            if not isinstance(ppath, list):
                ppath = [ppath]

            ppath = tuple(p for p in ppath if p not in active_path)
            ppath = ppath + active_path

        env['PYTHONPATH'] = list(ppath)
        set_user_env(listdict2envdict(env), parent=self)

    def get_path_dict(self, read_only=False):
        """
        Return an ordered dict with the path entries as keys and the active
        state as the value.

        If `read_only` is True, the read_only entries are also included.
        `read_only` entry refers to the project path entry.
        """
        odict = OrderedDict()
        for row in range(self.listwidget.count()):
            item = self.listwidget.item(row)
            path = item.text()
            if path in self.read_only_path and not read_only:
                continue
            odict[path] = item.checkState() == Qt.Checked
        return odict

    def refresh(self):
        """Refresh toolbar widgets."""
        enabled = self.listwidget.currentItem() is not None
        for widget in self.selection_widgets:
            widget.setEnabled(enabled)

        # Disable buttons based on row
        row = self.listwidget.currentRow()
        disable_widgets = []

        # Move up/top disabled for top item
        if row == 0:
            disable_widgets.extend([self.movetop_button, self.moveup_button])

        # Move down/bottom disabled for bottom item
        if row == self.editable_bottom_row:
            disable_widgets.extend(
                [self.movebottom_button, self.movedown_button])
        for widget in disable_widgets:
            widget.setEnabled(False)

        self.sync_button.setEnabled(self.listwidget.count() > 0)

        # Ok button only enabled if actual changes occur
        self.button_ok.setEnabled(
            self.original_path_dict != self.get_path_dict())

    def check_path(self, path):
        """Check that the path is not a [site|dist]-packages folder."""
        if os.name == 'nt':
            pat = re.compile(r'.*lib/(?:site|dist)-packages.*')
        else:
            pat = re.compile(r'.*lib/python.../(?:site|dist)-packages.*')

        path_norm = path.replace('\\', '/')
        return pat.match(path_norm) is None

    @Slot()
    def add_path(self, directory=None):
        """
        Add path to list widget.

        If `directory` is provided, the folder dialog is overridden.
        """
        if directory is None:
            self.redirect_stdio.emit(False)
            directory = getexistingdirectory(self, _("Select directory"),
                                             self.last_path)
            self.redirect_stdio.emit(True)

        if PY2:
            is_unicode = False
            try:
                directory.decode('ascii')
            except (UnicodeEncodeError, UnicodeDecodeError):
                is_unicode = True

            if is_unicode:
                QMessageBox.warning(
                    self, _("Add path"),
                    _("You are using Python 2 and the selected path has "
                      "Unicode characters."
                      "<br> "
                      "Therefore, this path will not be added."),
                    QMessageBox.Ok)
                return

        directory = osp.abspath(directory)
        self.last_path = directory

        if directory in self.get_path_dict():
            item = self.listwidget.findItems(directory, Qt.MatchExactly)[0]
            item.setCheckState(Qt.Checked)
            answer = QMessageBox.question(
                self, _("Add path"),
                _("This directory is already included in the list."
                  "<br> "
                  "Do you want to move it to the top of it?"),
                QMessageBox.Yes | QMessageBox.No)

            if answer == QMessageBox.Yes:
                item = self.listwidget.takeItem(self.listwidget.row(item))
                self.listwidget.insertItem(0, item)
                self.listwidget.setCurrentRow(0)
        else:
            if self.check_path(directory):
                item = self._create_item(directory)
                self.listwidget.insertItem(0, item)
                self.listwidget.setCurrentRow(0)
            else:
                answer = QMessageBox.warning(
                    self, _("Add path"),
                    _("This directory cannot be added to the path!"
                      "<br><br>"
                      "If you want to set a different Python interpreter, "
                      "please go to <tt>Preferences > Main interpreter</tt>"
                      "."), QMessageBox.Ok)

        self.refresh()

    @Slot()
    def remove_path(self, force=False):
        """
        Remove path from list widget.

        If `force` is True, the message box is overridden.
        """
        if self.listwidget.currentItem():
            if not force:
                answer = QMessageBox.warning(
                    self, _("Remove path"),
                    _("Do you really want to remove the selected path?"),
                    QMessageBox.Yes | QMessageBox.No)

            if force or answer == QMessageBox.Yes:
                self.listwidget.takeItem(self.listwidget.currentRow())
                self.refresh()

    def move_to(self, absolute=None, relative=None):
        """Move items of list widget."""
        index = self.listwidget.currentRow()
        if absolute is not None:
            if absolute:
                new_index = self.listwidget.count() - 1
            else:
                new_index = 0
        else:
            new_index = index + relative

        new_index = max(0, min(self.editable_bottom_row, new_index))
        item = self.listwidget.takeItem(index)
        self.listwidget.insertItem(new_index, item)
        self.listwidget.setCurrentRow(new_index)
        self.refresh()

    def current_row(self):
        """Returns the current row of the list."""
        return self.listwidget.currentRow()

    def set_current_row(self, row):
        """Set the current row of the list."""
        self.listwidget.setCurrentRow(row)

    def row_check_state(self, row):
        """Return the checked state for item in row."""
        item = self.listwidget.item(row)
        return item.checkState()

    def set_row_check_state(self, row, value):
        """Set the current checked state for item in row."""
        item = self.listwidget.item(row)
        item.setCheckState(value)

    def count(self):
        """Return the number of items."""
        return self.listwidget.count()

    def accept(self):
        """Override Qt method."""
        path_dict = self.get_path_dict()
        if self.original_path_dict != path_dict:
            self.sig_path_changed.emit(path_dict)
        super(PathManager, self).accept()