Beispiel #1
0
class TreeSymbolsWidget(QDialog):
    """Class of Dialog for Tree Symbols"""
    dockWidget = pyqtSignal("QObject*")
    undockWidget = pyqtSignal()
    changeTitle = pyqtSignal(str)
    def __init__(self, parent=None):
        super(TreeSymbolsWidget, self).__init__(parent,
                                                Qt.WindowStaysOnTopHint)
        vbox = QVBoxLayout(self)
        vbox.setContentsMargins(0, 0, 0, 0)
        vbox.setSpacing(0)
        self.tree = QTreeWidget()
        vbox.addWidget(self.tree)
        self.tree.header().setHidden(True)
        self.tree.setSelectionMode(QAbstractItemView.SingleSelection)
        self.tree.setAnimated(True)
        self.tree.header().setHorizontalScrollMode(
            QAbstractItemView.ScrollPerPixel)
        self.tree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.tree.header().setStretchLastSection(False)
        self.actualSymbols = ('', {})
        self.docstrings = {}
        self.collapsedItems = {}

        self.tree.itemClicked['QTreeWidgetItem*', int].connect(self._go_to_definition)
        # self.tree.itemActivated['QTreeWidgetItem*', int].connect(self._go_to_definition)
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested['const QPoint &'].connect(self._menu_context_tree)
        self.tree.itemCollapsed['QTreeWidgetItem*'].connect(self._item_collapsed)
        self.tree.itemExpanded['QTreeWidgetItem*'].connect(self._item_expanded)

        IDE.register_service('symbols_explorer', self)
        ExplorerContainer.register_tab(translations.TR_TAB_SYMBOLS, self)

    def install_tab(self):
        """Connect signals for goingdown"""
        ide = IDE.getInstance()
        ide.goingDown.connect(self.close)

    def _menu_context_tree(self, point):
        """Context menu"""
        index = self.tree.indexAt(point)
        if not index.isValid():
            return

        menu = QMenu(self)
        f_all = menu.addAction(translations.TR_FOLD_ALL)
        u_all = menu.addAction(translations.TR_UNFOLD_ALL)
        menu.addSeparator()
        u_class = menu.addAction(translations.TR_UNFOLD_CLASSES)
        u_class_method = menu.addAction(
            translations.TR_UNFOLD_CLASSES_AND_METHODS)
        u_class_attr = menu.addAction(
            translations.TR_UNFOLD_CLASSES_AND_ATTRIBUTES)
        menu.addSeparator()
        #save_state = menu.addAction(self.tr("Save State"))

        f_all.triggered['bool'].connect(lambda s: self.tree.collapseAll())
        u_all.triggered['bool'].connect(lambda s: self.tree.expandAll())
        u_class.triggered['bool'].connect(self._unfold_class)
        u_class_method.triggered['bool'].connect(self._unfold_class_method)
        u_class_attr.triggered['bool'].connect(self._unfold_class_attribute)
        #self.connect(save_state, SIGNAL("triggered()"),
            #self._save_symbols_state)

        menu.exec_(QCursor.pos())

    def _get_classes_root(self):
        """Return the root of classes"""
        class_root = None
        for i in range(self.tree.topLevelItemCount()):
            item = self.tree.topLevelItem(i)
            if item.isClass and not item.isClickable:
                class_root = item
                break
        return class_root

    def _unfold_class(self):
        """Method to Unfold Classes"""
        self.tree.collapseAll()
        classes_root = self._get_classes_root()
        if not classes_root:
            return

        classes_root.setExpanded(True)

    def _unfold_class_method(self):
        """Method to Unfold Methods"""
        self.tree.expandAll()
        classes_root = self._get_classes_root()
        if not classes_root:
            return
        #for each class!
        for i in range(classes_root.childCount()):
            class_item = classes_root.child(i)
            #for each attribute or functions
            for j in range(class_item.childCount()):
                item = class_item.child(j)
                #METHODS ROOT!!
                if not item.isMethod and not item.isClickable:
                    item.setExpanded(False)
                    break

    def _unfold_class_attribute(self):
        """Method to Unfold Attributes"""
        self.tree.expandAll()
        classes_root = self._get_classes_root()
        if not classes_root:
            return
        #for each class!
        for i in range(classes_root.childCount()):
            class_item = classes_root.child(i)
            #for each attribute or functions
            for j in range(class_item.childCount()):
                item = class_item.child(j)
                #ATTRIBUTES ROOT!!
                if not item.isAttribute and not item.isClickable:
                    item.setExpanded(False)
                    break

    def _save_symbols_state(self):
        """Method to Save a persistent Symbols state"""
        #filename = self.actualSymbols[0]
        #TODO: persist self.collapsedItems[filename] in QSettings
        pass

    def _get_expand(self, item):
        """
        Returns True or False to be used as setExpanded() with the items
        It method is based on the click that the user made in the tree
        """
        name = self._get_unique_name(item)
        filename = self.actualSymbols[0]
        collapsed_items = self.collapsedItems.get(filename, [])
        can_check = (not item.isClickable) or item.isClass or item.isMethod
        if can_check and name in collapsed_items:
            return False
        return True

    @staticmethod
    def _get_unique_name(item):
        """
        Returns a string used as unique name
        """
        # className_Attributes/className_Functions
        parent = item.parent()
        if parent:
            return "%s_%s" % (parent.text(0), item.text(0))
        return "_%s" % item.text(0)

    def update_symbols_tree(self, symbols, filename='', parent=None):
        """Method to Update the symbols on the Tree"""
        TIP = "{} {}"
        if not parent:
            if filename == self.actualSymbols[0] and \
                    self.actualSymbols[1] and not symbols:
                return

            if symbols == self.actualSymbols[1]:
                # Nothing new then return
                return

            # we have new symbols refresh it
            self.tree.clear()
            self.actualSymbols = (filename, symbols)
            self.docstrings = symbols.get('docstrings', {})
            parent = self.tree

        if 'attributes' in symbols:
            # print("\nsymbols['attributes']", symbols['attributes'])
            globalAttribute = ItemTree(parent, [translations.TR_ATTRIBUTES])
            globalAttribute.isClickable = False
            globalAttribute.isAttribute = True
            globalAttribute.setExpanded(self._get_expand(globalAttribute))
            globalAttribute.setToolTip(
                0, TIP.format(len(symbols['attributes']),
                              translations.TR_ATTRIBUTES))
            for glob in sorted(symbols['attributes']):
                globItem = ItemTree(globalAttribute, [glob],
                                    lineno=symbols['attributes'][glob])
                globItem.isAttribute = True
                globItem.setIcon(0, QIcon(":img/attribute"))
                globItem.setExpanded(self._get_expand(globItem))

        if 'functions' in symbols and symbols['functions']:
            functionsItem = ItemTree(parent, [translations.TR_FUNCTIONS])
            functionsItem.isClickable = False
            functionsItem.isMethod = True
            functionsItem.setExpanded(self._get_expand(functionsItem))
            functionsItem.setToolTip(0, TIP.format(len(symbols['functions']),
                                                   translations.TR_FUNCTIONS))
            for func in sorted(symbols['functions']):
                item = ItemTree(functionsItem, [func],
                                lineno=symbols['functions'][func]['lineno'])
                tooltip = self.create_tooltip(
                    func, symbols['functions'][func]['lineno'])
                item.isMethod = True
                item.setIcon(0, QIcon(":img/function"))
                item.setToolTip(0, tooltip)
                item.setExpanded(self._get_expand(item))
                self.update_symbols_tree(
                    symbols['functions'][func]['functions'], parent=item)
        if 'classes' in symbols and symbols['classes']:
            classItem = ItemTree(parent, [translations.TR_CLASSES])
            classItem.isClickable = False
            classItem.isClass = True
            classItem.setExpanded(self._get_expand(classItem))
            classItem.setToolTip(0, TIP.format(len(symbols['classes']),
                                               translations.TR_CLASSES))
            for claz in sorted(symbols['classes']):
                line_number = symbols['classes'][claz]['lineno']
                item = ItemTree(classItem, [claz], lineno=line_number)
                item.isClass = True
                tooltip = self.create_tooltip(claz, line_number)
                item.setToolTip(0, tooltip)
                item.setIcon(0, QIcon(":img/class"))
                item.setExpanded(self._get_expand(item))
                self.update_symbols_tree(symbols['classes'][claz]['members'],
                                         parent=item)

    def _go_to_definition(self, item):
        """Takes and item object and goes to definition on the editor"""
        main_container = IDE.get_service('main_container')
        print("\nprimero al clickear pas por aca!!", item.text(0))
        if item.isClickable and main_container:
            # main_container.editor_go_to_line(item.lineno - 1, True)
            main_container.editor_go_to_symbol_line(item.lineno - 1, item.text(0), True)

    def create_tooltip(self, name, lineno):
        """Takes a name and line number and returns a tooltip"""
        doc = self.docstrings.get(lineno, None)
        if doc is None:
            doc = ''
        else:
            doc = '\n' + doc
        tooltip = name + doc
        return tooltip

    def _item_collapsed(self, item):
        """When item collapsed"""
        self.tree.collapseItem(item)

        can_check = (not item.isClickable) or item.isClass or item.isMethod
        if can_check:
            n = self._get_unique_name(item)
            filename = self.actualSymbols[0]
            self.collapsedItems.setdefault(filename, [])
            if not n in self.collapsedItems[filename]:
                self.collapsedItems[filename].append(n)

    def _item_expanded(self, item):
        """When item expanded"""
        self.tree.expandItem(item)

        n = self._get_unique_name(item)
        filename = self.actualSymbols[0]
        if n in self.collapsedItems.get(filename, []):
            self.collapsedItems[filename].remove(n)
            if not len(self.collapsedItems[filename]):
                # no more items, free space
                del self.collapsedItems[filename]

    def clean(self):
        """
        Reset the tree and reset attributes
        """
        self.tree.clear()
        self.collapsedItems = {}

    def reject(self):
        if self.parent() is None:
            self.dockWidget.emit(self)

    def closeEvent(self, event):
        """On Close event handling"""
        self.dockWidget.emit(self)
        event.ignore()
class MainWindow(QMainWindow):
    def __init__(self, screen_height, screen_width, version, parent=None):
        super(MainWindow, self).__init__(parent)

        self.screen_width = screen_width
        self.screen_height = screen_height
        self.version = version

        # basic main window settings
        self.resize(MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT)
        self.setWindowTitle('Linguistica {}'.format(self.version))

        # lexicon and lexicon tree
        self.lexicon = None
        self.lexicon_tree = None
        self.initialize_lexicon_tree()

        # set up major display, parameter window, then load main window
        self.majorDisplay = QWidget()
        self.parameterWindow = QWidget()
        self.load_main_window()

        # 'File' menu and actions
        select_corpus_action = self.create_action(text='&Select corpus...',
                                                  slot=self.corpus_dir_dialog,
                                                  tip='Select a corpus file',
                                                  shortcut='Ctrl+N')
        select_wordlist_action = self.create_action(text='&Select wordlist...',
                                                    slot=self.wordlist_dir_dialog,
                                                    tip='Select a wordlist file',
                                                    shortcut='Ctrl+W')
        run_file_action = self.create_action(text='&Run...',
                                             slot=self.run_file,
                                             tip='Run the input file',
                                             shortcut='Ctrl+D')
        parameters_action = self.create_action(text='&Parameters...',
                                               slot=self.parameters_dialog,
                                               tip='Change parameters',
                                               shortcut='Ctrl+P')

        file_menu = self.menuBar().addMenu('&File')
        file_menu.addAction(select_corpus_action)
        file_menu.addAction(select_wordlist_action)
        file_menu.addAction(run_file_action)
        file_menu.addAction(parameters_action)

        self.status = self.statusBar()
        self.status.setSizeGripEnabled(False)
        self.status.showMessage('No input file loaded. To select one: File --> '
                                'Select corpus... or Select wordlist...')

    def initialize_lexicon_tree(self):
        self.lexicon_tree = QTreeWidget()
        self.lexicon_tree.setEnabled(True)
        self.lexicon_tree.setMinimumWidth(TREEWIDGET_WIDTH_MIN)
        self.lexicon_tree.setMaximumWidth(TREEWIDGET_WIDTH_MAX)
        self.lexicon_tree.setMinimumHeight(TREEWIDGET_HEIGHT_MIN)
        self.lexicon_tree.setHeaderLabel('')
        self.lexicon_tree.setItemsExpandable(True)
        # noinspection PyUnresolvedReferences
        self.lexicon_tree.itemClicked.connect(self.tree_item_clicked)

    def create_action(self, text=None, slot=None, tip=None, shortcut=None):
        """
        This create actions for the File menu, things like
        Read Corpus, Rerun Corpus etc
        """
        action = QAction(text, self)
        if shortcut:
            action.setShortcut(shortcut)
        if tip:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot:
            # noinspection PyUnresolvedReferences
            action.triggered.connect(slot)
        if shortcut:
            # noinspection PyUnresolvedReferences
            QShortcut(QKeySequence(shortcut), self).activated.connect(slot)
        return action

    def _get_filename_from_dialog(self, ftype='input'):
        self.determine_last_file()
        if self.last_file_path and self.last_file_type == ftype:
            open_dir = self.last_file_path
        else:
            open_dir = os.getcwd()
        # noinspection PyTypeChecker,PyCallByClass
        fname = QFileDialog.getOpenFileName(self,
                                            'Select the {} file'.format(ftype),
                                            open_dir)
        process_all_gui_events()

        # HACK: fname is supposed to be a string (at least according to the
        # PyQt5 documentation), but for some reason fname is a tuple.
        # So we need this hack to make sure that fname is a string of a filename
        # -- Jackson Lee, 2015/06/22

        # update: it's turned out that this behavior is due to compatibility
        # between PyQt and PySide. The "tuple" behavior is in line with the
        # newer API2 for PyQt. (PyQt on python 3 uses API2 by default.)
        # more here: http://srinikom.github.io/pyside-bz-archive/343.html
        # so perhaps we keep our current hack for now?
        # -- Jackson Lee, 2015/08/24

        if fname and any(fname) and (type(fname) is tuple):
            return fname[0]
        else:
            # if this hack isn't needed somehow...
            return fname

    def corpus_dir_dialog(self):
        """
        Pop up the "open a file" dialog and ask for which corpus text file
        to use
        """
        self.corpus_filename = self._get_filename_from_dialog(ftype='corpus')

        process_all_gui_events()

        if type(self.corpus_filename) != str:
            return

        # note that self.corpus_filename is an absolute full path
        self.corpus_name = os.path.basename(self.corpus_filename)
        self.corpus_stem_name = Path(self.corpus_name).stem

        self.lexicon = read_corpus(self.corpus_filename)
        self.initialize_lexicon_tree()
        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        process_all_gui_events()

        self.status.clearMessage()
        self.status.showMessage(
            'Corpus selected: {}'.format(self.corpus_filename))

    def wordlist_dir_dialog(self):
        """
        Pop up the "open a file" dialog and ask for which corpus text file
        to use
        """
        self.corpus_filename = self._get_filename_from_dialog(ftype='wordlist')

        process_all_gui_events()

        if type(self.corpus_filename) != str:
            return

        # note that self.corpus_filename is an absolute full path
        self.corpus_name = os.path.basename(self.corpus_filename)
        self.corpus_stem_name = Path(self.corpus_name).stem

        self.lexicon = read_wordlist(self.corpus_filename)
        self.initialize_lexicon_tree()
        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        process_all_gui_events()

        self.status.clearMessage()
        self.status.showMessage(
            'Wordlist selected: {}'.format(self.corpus_filename))

    def parameters_dialog(self):
        if self.lexicon is None:
            warning = QMessageBox()
            warning.setIcon(QMessageBox.Warning)
            warning.setText('Parameters can only be accessed when an input '
                            'file is specified.')
            warning.setWindowTitle('No input file selected')
            warning.setStandardButtons(QMessageBox.Ok)
            warning.exec_()
            return

        process_all_gui_events()

        parameters = self.lexicon.parameters()
        dialog = QDialog()
        layout = QVBoxLayout()
        layout.addWidget(
            QLabel('Filename: {}'.format(Path(self.corpus_filename).name)))
        file_type = 'Wordlist' if self.lexicon.file_is_wordlist else 'Corpus'
        layout.addWidget(QLabel('Type: {}'.format(file_type)))

        grid = QGridLayout()
        self.parameter_spinboxes = [QSpinBox() for _ in range(len(parameters))]

        for i, parameter_name in enumerate(sorted(parameters.keys())):
            self.parameter_spinboxes[i].setObjectName(parameter_name)
            self.parameter_spinboxes[i].setRange(
                *PARAMETERS_RANGES[parameter_name])
            self.parameter_spinboxes[i].setValue(parameters[parameter_name])
            self.parameter_spinboxes[i].setSingleStep(1)
            # noinspection PyUnresolvedReferences
            self.parameter_spinboxes[i].valueChanged.connect(
                self.update_parameter)

            grid.addWidget(QLabel(parameter_name), i, 0)
            grid.addWidget(self.parameter_spinboxes[i], i, 1)
            grid.addWidget(QLabel(PARAMETERS_HINTS[parameter_name]), i, 2)

        layout.addLayout(grid)

        reset_button = QPushButton()
        reset_button.setText('&Reset')
        # noinspection PyUnresolvedReferences
        reset_button.clicked.connect(self.reset_parameters)

        spacer = QWidget()  # just for padding in tool_bar
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        tool_bar = QHBoxLayout()
        tool_bar.addWidget(spacer)  # so that the buttons are right-aligned
        tool_bar.addWidget(reset_button)

        layout.addLayout(tool_bar)

        dialog.setLayout(layout)
        dialog.setWindowTitle('Parameters')
        dialog.exec_()

    def reset_parameters(self):
        self.lexicon.use_default_parameters()

        for i, (_, value) in \
                enumerate(sorted(self.lexicon.parameters().items())):
            self.parameter_spinboxes[i].setValue(value)

    def update_parameter(self):
        for i in range(len(self.lexicon.parameters())):
            parameter_name, new_value = \
                self.parameter_spinboxes[i].objectName(), \
                self.parameter_spinboxes[i].value()
            self.lexicon.change_parameters(**{parameter_name: new_value})

    def update_progress(self, progress_text, target_percentage):
        """
        Update the progress dialog. This function is triggered by the
        "progress_signal" emitted from the linguistica component worker thread.
        """
        self.progressDialog.setLabelText(progress_text)
        self.progressDialog.setValue(target_percentage)
        process_all_gui_events()

    # noinspection PyProtectedMember
    def run_file(self):
        if self.lexicon is None:
            warning = QMessageBox()
            warning.setIcon(QMessageBox.Warning)
            warning.setText('No input file is selected.')
            warning.setWindowTitle('Error')
            warning.setStandardButtons(QMessageBox.Ok)
            warning.exec_()
            return

        self.status.clearMessage()
        self.status.showMessage('Running the file {} now...'
                                .format(self.corpus_name))

        print('\nInput file in use:\n{}\n'.format(self.corpus_filename),
              flush=True)

        # set up the Linguistica components worker
        # The worker is a QThread. We spawn this thread, and the linguistica
        # components run on this new thread but not the main thread for the GUI.
        # This makes the GUI still responsive
        # while the long and heavy running process of
        # the Linguistica components is ongoing.

        self.lxa_worker = LinguisticaWorker(self.lexicon)
        self.lxa_worker.progress_signal.connect(self.update_progress)

        # set up progress dialog

        process_all_gui_events()
        self.progressDialog = QProgressDialog()
        self.progressDialog.setRange(0, 100)  # it's like from 0% to 100%
        self.progressDialog.setLabelText('Initializing...')
        self.progressDialog.setValue(0)  # initialize as 0 (= 0%)
        self.progressDialog.setWindowTitle(
            'Processing {}'.format(self.corpus_name))
        self.progressDialog.setCancelButton(None)
        self.progressDialog.resize(400, 100)
        process_all_gui_events()

        self.progressDialog.show()

        # We disable the "cancel" button
        # Setting up a "cancel" mechanism may not be a good idea,
        # since it would probably involve killing the linguistica component
        # worker at *any* point of its processing.
        # This may have undesirable effects (e.g., freezing the GUI) -- BAD!

        # make sure all GUI stuff up to this point has been processed before
        # doing the real work of running the Lxa components
        process_all_gui_events()

        # Now the real work begins here!
        self.lxa_worker.start()

        process_all_gui_events()

        self.lexicon = self.lxa_worker.get_lexicon()

        print('\nAll Linguistica components run for the file', flush=True)
        self.status.clearMessage()
        self.status.showMessage('{} processed'.format(self.corpus_name))

        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        self.populate_lexicon_tree()
        self.update_last_file()
        process_all_gui_events()

        # display corpus name (in the tree header label)
        file_type = 'wordlist' if self.lexicon.file_is_wordlist else 'corpus'
        header_label = 'File: {}\nFile type: {}\n\n# word types: {:,}\n'.format(
            self.corpus_name, file_type, self.lexicon.number_of_word_types())
        if file_type == 'corpus':
            header_label += '# word tokens: {:,}\n'.format(
                self.lexicon.number_of_word_tokens())
        self.lexicon_tree.setHeaderLabel(header_label)

    @staticmethod
    def ensure_config_dir_exists():
        if not os.path.isdir(CONFIG_DIR):
            os.mkdir(CONFIG_DIR)

    def determine_last_file(self):
        self.last_file_path = None
        self.last_file_type = None
        self.last_file_encoding = None

        if not os.path.isfile(CONFIG_LAST_FILE):
            return

        with open(CONFIG_LAST_FILE, encoding='utf8') as f:
            config_last_file = json.load(f)

        self.last_file_path = config_last_file['last_file_path']
        self.last_file_type = config_last_file['last_file_type']
        self.last_file_encoding = config_last_file['last_file_encoding']

    def update_last_file(self):
        self.ensure_config_dir_exists()
        with open(CONFIG_LAST_FILE, 'w', encoding='utf8') as f:
            if self.lexicon.file_is_wordlist:
                file_type = 'wordlist'
            else:
                file_type = 'corpus'

            config = {'last_file_path': self.lexicon.file_abspath,
                      'last_file_type': file_type,
                      'last_file_encoding': self.lexicon.encoding,
                      }

            json.dump(config, f)

    def populate_lexicon_tree(self):
        self.lexicon_tree.clear()
        process_all_gui_events()

        # wordlist
        ancestor = QTreeWidgetItem(self.lexicon_tree, [WORDLIST])
        self.lexicon_tree.expandItem(ancestor)

        # word ngrams
        ancestor = QTreeWidgetItem(self.lexicon_tree, [WORD_NGRAMS])
        self.lexicon_tree.expandItem(ancestor)
        for item_str in [BIGRAMS, TRIGRAMS]:
            item = QTreeWidgetItem(ancestor, [item_str])
            self.lexicon_tree.expandItem(item)

        # signatures
        ancestor = QTreeWidgetItem(self.lexicon_tree, [SIGNATURES])
        self.lexicon_tree.expandItem(ancestor)
        for item in [SIGS_TO_STEMS, WORDS_TO_SIGS]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        # tries
        ancestor = QTreeWidgetItem(self.lexicon_tree, [TRIES])
        self.lexicon_tree.expandItem(ancestor)
        for item in [WORDS_AS_TRIES, SUCCESSORS, PREDECESSORS]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        # phonology
        ancestor = QTreeWidgetItem(self.lexicon_tree, [PHONOLOGY])
        self.lexicon_tree.expandItem(ancestor)
        for item in [PHONES, BIPHONES, TRIPHONES]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        # manifolds
        ancestor = QTreeWidgetItem(self.lexicon_tree, [MANIFOLDS])
        self.lexicon_tree.expandItem(ancestor)
        for item in [WORD_NEIGHBORS, VISUALIZED_GRAPH]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        self.status.clearMessage()
        self.status.showMessage('Navigation tree populated')
        print('Lexicon navigation tree populated', flush=True)

    def load_main_window(self, major_display=None, parameter_window=None):
        """
        Refresh the main window for the updated display content
        (most probably after a click or some event is triggered)
        """
        # get sizes of the three major PyQt objects
        major_display_size = self.majorDisplay.size()
        parameter_window_size = self.parameterWindow.size()
        lexicon_tree_size = self.lexicon_tree.size()

        if major_display:
            self.majorDisplay = major_display
        if parameter_window:
            self.parameterWindow = parameter_window

        # apply sizes to the major three objects
        self.majorDisplay.resize(major_display_size)
        self.parameterWindow.resize(parameter_window_size)
        self.lexicon_tree.resize(lexicon_tree_size)

        # set up:
        # 1) main splitter (b/w lexicon-tree+parameter window and major display)
        # 2) minor splitter (b/w lexicon-tree and parameter window)
        self.mainSplitter = QSplitter(Qt.Horizontal)
        self.mainSplitter.setHandleWidth(10)
        self.mainSplitter.setChildrenCollapsible(False)

        self.minorSplitter = QSplitter(Qt.Vertical)
        self.minorSplitter.setHandleWidth(10)
        self.minorSplitter.setChildrenCollapsible(False)

        self.minorSplitter.addWidget(self.lexicon_tree)
        self.minorSplitter.addWidget(self.parameterWindow)

        self.mainSplitter.addWidget(self.minorSplitter)
        self.mainSplitter.addWidget(self.majorDisplay)

        self.setCentralWidget(self.mainSplitter)

    def sig_to_stems_clicked(self, row):
        signature = self.sig_to_stems_major_table.item(row, 0).text()
        print(signature)
        signature = tuple(signature.split(SEP_SIG))

        stems = sorted(self.lexicon.signatures_to_stems()[signature])
        number_of_stems_per_column = 5

        # create a master list of sublists, where each sublist contains k stems
        # k = number_of_stems_per_column
        stem_rows = list()
        stem_row = list()

        for i, stem in enumerate(stems, 1):
            stem_row.append(stem)
            if not i % number_of_stems_per_column:
                stem_rows.append(stem_row)
                stem_row = list()
        if stem_row:
            stem_rows.append(stem_row)

        # set up the minor table as table widget
        sig_to_stems_minor_table = QTableWidget()
        sig_to_stems_minor_table.horizontalHeader().hide()
        sig_to_stems_minor_table.verticalHeader().hide()
        sig_to_stems_minor_table.clear()
        sig_to_stems_minor_table.setRowCount(len(stem_rows))
        sig_to_stems_minor_table.setColumnCount(number_of_stems_per_column)

        # fill in the minor table
        for row, stem_row in enumerate(stem_rows):
            for col, stem in enumerate(stem_row):
                item = QTableWidgetItem(stem)
                sig_to_stems_minor_table.setItem(row, col, item)

        sig_to_stems_minor_table.resizeColumnsToContents()

        minor_table_title = QLabel('{} (number of stems: {})'
                                   .format(SEP_SIG.join(signature), len(stems)))

        minor_table_widget_with_title = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(minor_table_title)
        layout.addWidget(sig_to_stems_minor_table)
        minor_table_widget_with_title.setLayout(layout)

        new_display = QSplitter(Qt.Horizontal)
        new_display.setHandleWidth(10)
        new_display.setChildrenCollapsible(False)

        new_display.addWidget(self.sig_to_stems_major_table)
        new_display.addWidget(minor_table_widget_with_title)
        new_display_width = self.majorDisplay.width() / 2
        new_display.setSizes(
            [new_display_width * 0.4, new_display_width * 0.6])

        self.load_main_window(major_display=new_display)
        self.status.clearMessage()
        self.status.showMessage('{} selected'.format(signature))

    def unavailable_for_wordlist(self):
        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        self.status.showMessage('')
        warning = QMessageBox()
        warning.setIcon(QMessageBox.Warning)
        warning.setText('Unavailable for a wordlist')
        warning.setWindowTitle('Error')
        warning.setStandardButtons(QMessageBox.Ok)
        warning.exec_()

    def tree_item_clicked(self, item):
        """
        Trigger the appropriate action when something in the lexicon tree
        is clicked, and update the major display plus parameter window
        """
        item_str = item.text(0)

        if item_str in {WORD_NGRAMS, SIGNATURES, TRIES, PHONOLOGY, MANIFOLDS}:
            return

        print('loading', item_str, flush=True)

        self.status.clearMessage()
        self.status.showMessage('Loading {}...'.format(item_str))

        new_display = None
        new_parameter_window = None

        if item_str == WORDLIST:
            new_display = self.create_major_display_table(
                self.lexicon.word_phonology_dict().items(),
                key=lambda x: x[1].count, reverse=True,
                headers=['Word', 'Count', 'Frequency', 'Phones',
                         'Unigram plog', 'Avg unigram plog',
                         'Bigram plog', 'Avg bigram plog'],
                row_cell_functions=[
                    lambda x: x[0], lambda x: x[1].count,
                    lambda x: x[1].frequency,
                    lambda x: ' '.join(x[1].phones),
                    lambda x: x[1].unigram_plog,
                    lambda x: x[1].avg_unigram_plog,
                    lambda x: x[1].bigram_plog,
                    lambda x: x[1].avg_bigram_plog],
                cutoff=0)

        elif item_str == BIGRAMS:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return
            new_display = self.create_major_display_table(
                self.lexicon.word_bigram_counter().items(),
                key=lambda x: x[1], reverse=True,
                headers=['Bigram', 'Count'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1]],
                cutoff=2000)

        elif item_str == TRIGRAMS:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return
            new_display = self.create_major_display_table(
                self.lexicon.word_trigram_counter().items(),
                key=lambda x: x[1], reverse=True,
                headers=['Trigram', 'Count'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1]],
                cutoff=2000)

        elif item_str == SIGS_TO_STEMS:
            self.sig_to_stems_major_table = self.create_major_display_table(
                self.lexicon.signatures_to_stems().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['Signature', 'Stem count', 'A few stems'],
                row_cell_functions=[lambda x: SEP_SIG.join(x[0]),
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join(sorted(x[1])[:2]) +
                                              ', ...'],
                cutoff=0)
            # noinspection PyUnresolvedReferences
            self.sig_to_stems_major_table.cellClicked.connect(
                self.sig_to_stems_clicked)
            new_display = self.sig_to_stems_major_table

        elif item_str == WORDS_TO_SIGS:
            new_display = self.create_major_display_table(
                self.lexicon.words_to_signatures().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['Word', 'Signature count', 'Signatures'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join([SEP_SIG.join(sig)
                                                         for sig in
                                                         sorted(x[1])])],
                cutoff=2000)

        elif item_str == WORDS_AS_TRIES:
            words = self.lexicon.broken_words_left_to_right().keys()
            words_to_tries = dict()
            # key: word (str)
            # value: tuple of (str, str)
            # for left-to-right and right-to-left tries

            for word in words:
                l_r = ' '.join(self.lexicon.broken_words_left_to_right()[word])
                r_l = ' '.join(self.lexicon.broken_words_right_to_left()[word])
                words_to_tries[word] = (l_r, r_l)  # left-right, right-left

            new_display = self.create_major_display_table(
                words_to_tries.items(),
                key=lambda x: x[0], reverse=False,
                headers=['Word', 'Reversed word',
                         'Left-to-right trie', 'Right-to-left trie'],
                row_cell_functions=[lambda x: x[0], lambda x: x[0][::-1],
                                    lambda x: x[1][0], lambda x: x[1][1]],
                cutoff=0, set_text_alignment=[(3, Qt.AlignRight)])

        elif item_str == SUCCESSORS:
            new_display = self.create_major_display_table(
                self.lexicon.successors().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['String', 'Successor count', 'Successors'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join(sorted(x[1]))],
                cutoff=0)

        elif item_str == PREDECESSORS:
            new_display = self.create_major_display_table(
                self.lexicon.predecessors().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['String', 'Predecessor count', 'Predecessors'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join(sorted(x[1]))],
                cutoff=0)

        elif item_str == PHONES:
            new_display = self.create_major_display_table(
                self.lexicon.phone_dict().items(),
                key=lambda x: x[1].count, reverse=True,
                headers=['Phone', 'Count', 'Frequency', 'Plog'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: x[1].count,
                                    lambda x: x[1].frequency,
                                    lambda x: x[1].plog],
                cutoff=0)

        elif item_str == BIPHONES:
            new_display = self.create_major_display_table(
                self.lexicon.biphone_dict().items(),
                key=lambda x: x[1].count, reverse=True,
                headers=['Biphone', 'Count', 'Frequency',
                         'Mutual information (MI)', 'Weighted MI'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1].count,
                                    lambda x: x[1].frequency,
                                    lambda x: x[1].MI,
                                    lambda x: x[1].weighted_MI],
                cutoff=0)

        elif item_str == TRIPHONES:
            new_display = self.create_major_display_table(
                self.lexicon.phone_trigram_counter().items(),
                key=lambda x: x[1], reverse=True,
                headers=['Triphone', 'Count'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1]],
                cutoff=0)

        elif item_str == WORD_NEIGHBORS:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return
            word_to_freq = self.lexicon.word_unigram_counter()
            new_display = self.create_major_display_table(
                self.lexicon.words_to_neighbors().items(),
                key=lambda x: word_to_freq[x[0]], reverse=True,
                headers=['Word', 'Word count', 'Neighbors'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: word_to_freq[x[0]],
                                    lambda x: ' '.join(x[1])],
                cutoff=0)

        elif item_str == VISUALIZED_GRAPH:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return

            graph_width = self.screen_width - TREEWIDGET_WIDTH_MAX - 50
            graph_height = self.screen_height - 70
            html_name = 'show_manifold.html'

            manifold_name = '{}_manifold.json'.format(self.corpus_stem_name)
            manifold_filename = os.path.join(CONFIG_DIR, manifold_name)
            print('manifold_filename', manifold_filename)

            manifold_json_data = json_graph.node_link_data(
                self.lexicon.neighbor_graph())
            json.dump(manifold_json_data, open(manifold_filename, 'w'))

            viz_html = os.path.join(CONFIG_DIR, html_name)
            print('viz_html', viz_html)

            # write the show_manifold html file
            with open(viz_html, 'w') as f:
                print(SHOW_MANIFOLD_HTML.format(os.path.dirname(__file__),
                                                graph_width, graph_height,
                                                manifold_filename), file=f)

            url = Path(viz_html).as_uri()
            print('url:', url)

            new_display = QWebView()
            new_display.setUrl(QUrl(url))

        self.load_main_window(major_display=new_display,
                              parameter_window=new_parameter_window)

        self.status.clearMessage()
        self.status.showMessage('{} selected'.format(item_str))

    @staticmethod
    def create_major_display_table(input_iterable,
                                   key=lambda x: x, reverse=False,
                                   headers=None, row_cell_functions=None,
                                   cutoff=0,
                                   set_text_alignment=None):
        """
        This is a general function for creating a tabular display for the
        major display.
        """

        if not input_iterable:
            print('Warning: input is empty', flush=True)
            return

        if not hasattr(input_iterable, '__iter__'):
            print('Warning: input is not an iterable', flush=True)
            return

        number_of_headers = len(headers)
        number_of_columns = len(row_cell_functions)

        if number_of_headers != number_of_columns:
            print('headers and cell functions don\'t match', flush=True)
            return

        len_input = len(input_iterable)

        table_widget = QTableWidget()
        table_widget.clear()
        table_widget.setSortingEnabled(False)

        # set up row count
        if cutoff and cutoff < len_input:
            actual_cutoff = cutoff
        else:
            actual_cutoff = len_input

        table_widget.setRowCount(actual_cutoff)

        # set up column count and table headers
        table_widget.setColumnCount(number_of_headers)
        table_widget.setHorizontalHeaderLabels(headers)

        # fill in the table
        for row, x in enumerate(double_sorted(input_iterable, key=key,
                                              reverse=reverse)):
            for col, fn in enumerate(row_cell_functions):
                cell = fn(x)

                if isinstance(cell, (int, float)):
                    # cell is numeric
                    item = QTableWidgetItem()
                    item.setData(Qt.EditRole, cell)
                else:
                    # cell is not numeric
                    item = QTableWidgetItem(cell)

                if set_text_alignment:
                    for align_col, alignment in set_text_alignment:
                        if col == align_col:
                            item.setTextAlignment(alignment)

                table_widget.setItem(row, col, item)

            if not row < actual_cutoff:
                break

        table_widget.setSortingEnabled(True)
        table_widget.resizeColumnsToContents()

        return table_widget
Beispiel #3
0
class MainForm(QDialog):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        listLabel = QLabel("&List")
        self.listWidget = QListWidget()
        listLabel.setBuddy(self.listWidget)

        tableLabel = QLabel("&Table")
        self.tableWidget = QTableWidget()
        tableLabel.setBuddy(self.tableWidget)

        treeLabel = QLabel("Tre&e")
        self.treeWidget = QTreeWidget()
        treeLabel.setBuddy(self.treeWidget)

        addShipButton = QPushButton("&Add Ship")
        removeShipButton = QPushButton("&Remove Ship")
        quitButton = QPushButton("&Quit")
        if not MAC:
            addShipButton.setFocusPolicy(Qt.NoFocus)
            removeShipButton.setFocusPolicy(Qt.NoFocus)
            quitButton.setFocusPolicy(Qt.NoFocus)

        splitter = QSplitter(Qt.Horizontal)
        vbox = QVBoxLayout()
        vbox.addWidget(listLabel)
        vbox.addWidget(self.listWidget)
        widget = QWidget()
        widget.setLayout(vbox)
        splitter.addWidget(widget)
        vbox = QVBoxLayout()
        vbox.addWidget(tableLabel)
        vbox.addWidget(self.tableWidget)
        widget = QWidget()
        widget.setLayout(vbox)
        splitter.addWidget(widget)
        vbox = QVBoxLayout()
        vbox.addWidget(treeLabel)
        vbox.addWidget(self.treeWidget)
        widget = QWidget()
        widget.setLayout(vbox)
        splitter.addWidget(widget)
        buttonLayout = QHBoxLayout()
        buttonLayout.addWidget(addShipButton)
        buttonLayout.addWidget(removeShipButton)
        buttonLayout.addStretch()
        buttonLayout.addWidget(quitButton)
        layout = QVBoxLayout()
        layout.addWidget(splitter)
        layout.addLayout(buttonLayout)
        self.setLayout(layout)

        self.tableWidget.itemChanged[QTableWidgetItem].connect(
            self.tableItemChanged)
        addShipButton.clicked.connect(self.addShip)
        removeShipButton.clicked.connect(self.removeShip)
        quitButton.clicked.connect(self.accept)

        self.ships = ships.ShipContainer("ships.dat")
        self.setWindowTitle("Ships (dict)")
        QTimer.singleShot(0, self.initialLoad)

    def initialLoad(self):
        if not QFile.exists(self.ships.filename):
            for ship in ships.generateFakeShips():
                self.ships.addShip(ship)
            self.ships.dirty = False
        else:
            try:
                self.ships.load()
            except IOError as e:
                QMessageBox.warning(self, "Ships - Error",
                                    "Failed to load: {0}".format(e))
        self.populateList()
        self.populateTable()
        self.tableWidget.sortItems(0)
        self.populateTree()

    def reject(self):
        self.accept()

    def accept(self):
        if (self.ships.dirty and QMessageBox.question(
                self, "Ships - Save?", "Save unsaved changes?",
                QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes):
            try:
                self.ships.save()
            except IOError as e:
                QMessageBox.warning(self, "Ships - Error",
                                    "Failed to save: {0}".format(e))
        QDialog.accept(self)

    def populateList(self, selectedShip=None):
        selected = None
        self.listWidget.clear()
        for ship in self.ships.inOrder():
            item = QListWidgetItem("{0} of {1}/{2} ({3:,})".format(
                ship.name, ship.owner, ship.country, int(ship.teu)))
            self.listWidget.addItem(item)
            if selectedShip is not None and selectedShip == id(ship):
                selected = item
        if selected is not None:
            selected.setSelected(True)
            self.listWidget.setCurrentItem(selected)

    def populateTable(self, selectedShip=None):
        selected = None
        self.tableWidget.clear()
        self.tableWidget.setSortingEnabled(False)
        self.tableWidget.setRowCount(len(self.ships))
        headers = ["Name", "Owner", "Country", "Description", "TEU"]
        self.tableWidget.setColumnCount(len(headers))
        self.tableWidget.setHorizontalHeaderLabels(headers)
        for row, ship in enumerate(self.ships):
            item = QTableWidgetItem(ship.name)
            item.setData(Qt.UserRole, id(ship))
            if selectedShip is not None and selectedShip == id(ship):
                selected = item
            self.tableWidget.setItem(row, ships.NAME, item)
            self.tableWidget.setItem(row, ships.OWNER,
                                     QTableWidgetItem(ship.owner))
            self.tableWidget.setItem(row, ships.COUNTRY,
                                     QTableWidgetItem(ship.country))
            self.tableWidget.setItem(row, ships.DESCRIPTION,
                                     QTableWidgetItem(ship.description))
            item = QTableWidgetItem("{0:>8}".format(ship.teu))
            item.setTextAlignment(Qt.AlignRight | Qt.AlignVCenter)
            self.tableWidget.setItem(row, ships.TEU, item)
        self.tableWidget.setSortingEnabled(True)
        self.tableWidget.resizeColumnsToContents()
        if selected is not None:
            selected.setSelected(True)
            self.tableWidget.setCurrentItem(selected)

    def populateTree(self, selectedShip=None):
        selected = None
        self.treeWidget.clear()
        self.treeWidget.setColumnCount(2)
        self.treeWidget.setHeaderLabels(["Country/Owner/Name", "TEU"])
        self.treeWidget.setItemsExpandable(True)
        parentFromCountry = {}
        parentFromCountryOwner = {}
        for ship in self.ships.inCountryOwnerOrder():
            ancestor = parentFromCountry.get(ship.country)
            if ancestor is None:
                ancestor = QTreeWidgetItem(self.treeWidget, [ship.country])
                parentFromCountry[ship.country] = ancestor
            countryowner = ship.country + "/" + ship.owner
            parent = parentFromCountryOwner.get(countryowner)
            if parent is None:
                parent = QTreeWidgetItem(ancestor, [ship.owner])
                parentFromCountryOwner[countryowner] = parent
            item = QTreeWidgetItem(parent, [ship.name, "{0}".format(ship.teu)])
            item.setTextAlignment(1, Qt.AlignRight | Qt.AlignVCenter)
            if selectedShip is not None and selectedShip == id(ship):
                selected = item
            self.treeWidget.expandItem(parent)
            self.treeWidget.expandItem(ancestor)
        self.treeWidget.resizeColumnToContents(0)
        self.treeWidget.resizeColumnToContents(1)
        if selected is not None:
            selected.setSelected(True)
            self.treeWidget.setCurrentItem(selected)
        print(parentFromCountry)
        print(parentFromCountryOwner)

    def addShip(self):
        ship = ships.Ship(" Unknown", " Unknown", " Unknown")
        self.ships.addShip(ship)
        self.populateList()
        self.populateTree()
        self.populateTable(id(ship))
        self.tableWidget.setFocus()
        self.tableWidget.editItem(self.tableWidget.currentItem())

    def tableItemChanged(self, item):
        ship = self.currentTableShip()
        if ship is None:
            return
        column = self.tableWidget.currentColumn()
        if column == ships.NAME:
            ship.name = item.text().strip()
        elif column == ships.OWNER:
            ship.owner = item.text().strip()
        elif column == ships.COUNTRY:
            ship.country = item.text().strip()
        elif column == ships.DESCRIPTION:
            ship.description = item.text().strip()
        elif column == ships.TEU:
            ship.teu = item.text()
        self.ships.dirty = True
        self.populateList()
        self.populateTree()

    def currentTableShip(self):
        item = self.tableWidget.item(self.tableWidget.currentRow(), 0)
        if item is None:
            return None
        return self.ships.ship(item.data(Qt.UserRole))

    def removeShip(self):
        ship = self.currentTableShip()
        if ship is None:
            return
        if (QMessageBox.question(
                self, "Ships - Remove",
                "Remove {0} of {1}/{2}?".format(ship.name, ship.owner,
                                                ship.country),
                QMessageBox.Yes | QMessageBox.No) == QMessageBox.No):
            return
        self.ships.removeShip(ship)
        self.populateList()
        self.populateTree()
        self.populateTable()
    def setupTab6(self, tab):
        """Advance widgets for preview panel"""
        container = QHBoxLayout()
        scrollArea = QScrollArea()
        scrollArea.setWidgetResizable(True)
        w = QWidget()
        w.setMinimumSize(QSize(400, 500))
        layout = QVBoxLayout()
        w.setLayout(layout)
        scrollArea.setWidget(w)
        container.addWidget(scrollArea)
        tab.setLayout(container)

        # List
        lay = QHBoxLayout()
        layout.addLayout(lay)
        list1 = QListWidget()
        list1.addItems(["aaa", "bbb", "ccc"])
        list2 = QListWidget()
        list2.addItem(
            QListWidgetItem(QIcon(":appres.img/Flag_blueHS.png"), "blue"))
        list2.addItem(
            QListWidgetItem(QIcon(":appres.img/Flag_redHS.png"), "red"))
        list2.addItem(
            QListWidgetItem(QIcon(":appres.img/Flag_greenHS.png"), "green"))
        list2.setViewMode(QListWidget.IconMode)
        lay.addWidget(list1)
        lay.addWidget(list2)

        # Table
        lay = QHBoxLayout()
        layout.addLayout(lay)
        t1 = QTableWidget()
        t1.setRowCount(3)
        t1.setColumnCount(3)
        for i in range(3):
            for j in range(3):
                t1.setItem(i, j, QTableWidgetItem(str((i + 1) * (j + 1))))
                t1.item(i, j).setTextAlignment(Qt.AlignCenter)
        t1.setColumnWidth(0, 50)
        t1.setColumnWidth(1, 50)
        t1.setColumnWidth(2, 50)
        t1.setEditTriggers(QTableWidget.AllEditTriggers)
        t2 = QTableWidget()
        t2.setRowCount(3)
        t2.setColumnCount(3)
        t2.setHorizontalHeaderLabels(["Name", "Gender", "Age"])
        t2.setVerticalHeaderLabels(["1st", "2rd", "3th"])
        t2.setItem(0, 0, QTableWidgetItem("july"))
        c = QComboBox()
        c.addItems(["Male", "Famale"])
        t2.setCellWidget(0, 1, c)
        t2.cellWidget(0, 1).setCurrentIndex(1)
        t2.setItem(0, 2, QTableWidgetItem("10"))
        t2.setItem(1, 0, QTableWidgetItem("john"))
        c = QComboBox()
        c.addItems(["Male", "Famale"])
        c.setEditable(True)
        t2.setCellWidget(1, 1, c)
        t2.setItem(1, 2, QTableWidgetItem("11"))
        t2.resizeColumnsToContents()
        t2.setEditTriggers(QTableWidget.EditKeyPressed
                           | QTableWidget.SelectedClicked
                           | QTableWidget.AnyKeyPressed
                           | QTableWidget.DoubleClicked)

        lay.addWidget(t1)
        lay.addWidget(t2)

        # Tree
        lay = QHBoxLayout()
        layout.addLayout(lay)
        tree1 = QTreeWidget()
        tree1.setColumnCount(2)
        tree1.setHeaderLabels(["Key", "Value"])
        node1 = QTreeWidgetItem()
        node1.setText(0, "root")
        node1.setText(1, "0")
        node1.setIcon(0, QIcon(":appres.img/home.png"))
        tree1.addTopLevelItem(node1)
        node11 = QTreeWidgetItem()
        node11.setText(0, "child1")
        icon = QIcon(":appres.img/book_angle.png")
        icon.addPixmap(QPixmap(":appres.img/book_open.png"), QIcon.Normal,
                       QIcon.On)
        node11.setIcon(0, icon)
        nodea = QTreeWidgetItem()
        nodea.setText(0, "red")
        nodea.setBackground(1, QBrush(Qt.red))
        nodeb = QTreeWidgetItem()
        nodeb.setText(0, "gray")
        nodeb.setBackground(1, QBrush(Qt.gray))
        nodec = QTreeWidgetItem()
        nodec.setText(0, "green")
        nodec.setBackground(1, QBrush(Qt.green))
        node11.addChildren([nodea, nodeb, nodec])
        node12 = QTreeWidgetItem()
        node12.setText(0, "child2")
        node12.setText(1, "child2")
        node13 = QTreeWidgetItem()
        node13.setText(0, "child3")
        node13.setText(1, "child3")
        node12.setIcon(0, icon)
        node13.setIcon(0, icon)
        node1.addChild(node11)
        node1.addChild(node12)
        node1.addChild(node13)
        tree1.expand(tree1.model().index(0, 0))
        tree1.expandItem(node11)
        tree2 = QTreeView()
        folder = QDirModel()
        tree2.setModel(folder)
        lay.addWidget(tree1)
        lay.addWidget(tree2)
Beispiel #5
0
class CircleFitResults(QGroupBox):
    """
    The tree displaying the Circle Fit results and the parameters used for
    automatically fitting the circle.

    Attributes
    ----------
    sig_selected_peak_changed : pyqtSignal(int)
        The signal emitted when the selected peak is changed.
    sig_recalculate_fit : pyqtSignal
        Signal emitted when the fit is forced to be recalculated.
    sig_add_new_peak : pyqtSignal
        Signal emitted when a new peak is added.
    sig_manual_parameter_change : pyqtSignal
        Signal emitted when a parameter is changed by hand.
    """

    sig_selected_peak_changed = pyqtSignal(int)
    sig_recalculate_fit = pyqtSignal()
    sig_add_new_peak = pyqtSignal()
    sig_manual_parameter_change = pyqtSignal()

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

        self.channels = []
        self.num_peaks = 0

        self.init_ui()

    def init_ui(self):
        # # Tree for values
        self.tree = QTreeWidget()
        self.tree.setHeaderLabels([
            "Name", "Frequency (Hz)", "Damping ratio", "Amplitude",
            "Phase (deg)", "Select"
        ])

        self.tree.setStyleSheet("QTreeWidget::item:has-children "
                                "{ background-color : palette(mid);}")

        # # Tree for autofit parameters
        self.autofit_tree = QTreeWidget()
        self.autofit_tree.setHeaderLabels([
            "Name", "Frequency (Hz)", "Damping ratio", "Amplitude",
            "Phase (deg)", "Select"
        ])

        self.autofit_tree.hide()
        self.autofit_tree.setStyleSheet("QTreeWidget::item:has-children "
                                        "{ background-color : palette(mid);}")

        # Connect the two trees together, so the views look identical
        self.autofit_tree.itemCollapsed.connect(self.on_item_collapsed)
        self.autofit_tree.itemExpanded.connect(self.on_item_expanded)
        self.tree.itemCollapsed.connect(self.on_item_collapsed)
        self.tree.itemExpanded.connect(self.on_item_expanded)

        # # Controls
        self.add_peak_btn = QPushButton("Add new peak", self)
        self.add_peak_btn.clicked.connect(self.add_peak)

        self.delete_selected_btn = QPushButton("Delete selected", self)
        self.delete_selected_btn.clicked.connect(self.delete_selected)

        self.view_autofit_btn = QPushButton("View autofit parameters", self)
        self.view_autofit_btn.clicked.connect(self.toggle_view)

        self.reset_to_autofit_btn = QPushButton("Reset all to auto", self)
        self.reset_to_autofit_btn.clicked.connect(self.reset_to_autofit)
        self.reset_to_autofit_btn.hide()

        controls1 = QGridLayout()
        controls1.addWidget(self.add_peak_btn, 0, 0)
        controls1.setColumnStretch(1, 1)
        controls1.addWidget(self.delete_selected_btn, 0, 2)

        controls2 = QGridLayout()
        controls2.setColumnStretch(0, 1)
        controls2.addWidget(self.reset_to_autofit_btn, 0, 1)
        controls2.addWidget(self.view_autofit_btn, 0, 2)

        # # Layout
        layout = QGridLayout()
        layout.addLayout(controls2, 0, 0)
        layout.addWidget(self.tree, 1, 0)
        layout.addWidget(self.autofit_tree, 1, 0)
        layout.addLayout(controls1, 2, 0)

        self.setLayout(layout)

    def add_peak(self):
        """Add a top-level item for a new peak to the tree, with children for
        each channel."""
        # Create the parent item for the peak
        peak_item = QTreeWidgetItem(
            self.tree, ["Peak {}".format(self.num_peaks), "", "", "", "", ""])
        autofit_peak_item = QTreeWidgetItem(
            self.autofit_tree,
            ["Peak {}".format(self.num_peaks), "", "", "", "", ""])

        # Put a radio button in column 5 in the tree
        radio_btn = QRadioButton()
        radio_btn.toggled.connect(self.on_selected_peak_changed)
        self.tree.setItemWidget(peak_item, 5, radio_btn)
        radio_btn.setChecked(True)

        for col in [1, 2, 3, 4]:
            # Put comboboxes in the autofit tree
            combobox = QComboBox()
            combobox.addItems(["Auto", "Manual"])
            combobox.currentIndexChanged.connect(self.update_peak_average)
            self.autofit_tree.setItemWidget(autofit_peak_item, col, combobox)

        # Put spinboxes in the tree
        freq_spinbox = QDoubleSpinBox()
        freq_spinbox.setRange(0, 9e99)
        freq_spinbox.valueChanged.connect(
            self.sig_manual_parameter_change.emit)
        self.tree.setItemWidget(peak_item, 1, freq_spinbox)
        damping_spinbox = QDoubleSpinBox()
        damping_spinbox.setRange(0, 9e99)
        damping_spinbox.setDecimals(5)
        damping_spinbox.setSingleStep(0.00001)
        damping_spinbox.valueChanged.connect(
            self.sig_manual_parameter_change.emit)
        self.tree.setItemWidget(peak_item, 2, damping_spinbox)
        amp_spinbox = QDoubleSpinBox()
        amp_spinbox.setRange(0, 9e99)
        amp_spinbox.valueChanged.connect(self.sig_manual_parameter_change.emit)
        self.tree.setItemWidget(peak_item, 3, amp_spinbox)
        phase_spinbox = QDoubleSpinBox()
        phase_spinbox.setRange(-180, 180)
        phase_spinbox.valueChanged.connect(
            self.sig_manual_parameter_change.emit)
        phase_spinbox.setWrapping(True)
        self.tree.setItemWidget(peak_item, 4, phase_spinbox)

        # Create the child items for each channel for this peak if there's
        # more than one channel
        if len(self.channels) > 1:
            for channel in self.channels:
                channel_item = QTreeWidgetItem(
                    peak_item, [channel.name, "", "", "", "", ""])
                autofit_channel_item = QTreeWidgetItem(
                    autofit_peak_item, [channel.name, "", "", "", "", ""])
                for col in [1, 2, 3, 4]:
                    # Put comboboxes in the autofit tree
                    combobox = QComboBox()
                    combobox.addItems(["Auto", "Manual"])
                    combobox.currentIndexChanged.connect(
                        self.update_peak_average)
                    self.autofit_tree.setItemWidget(autofit_channel_item, col,
                                                    combobox)

                freq_spinbox = QDoubleSpinBox()
                freq_spinbox.setRange(0, 9e99)
                freq_spinbox.valueChanged.connect(
                    self.sig_manual_parameter_change.emit)
                freq_spinbox.valueChanged.connect(self.update_peak_average)
                self.tree.setItemWidget(channel_item, 1, freq_spinbox)
                damping_spinbox = QDoubleSpinBox()
                damping_spinbox.setRange(0, 9e99)
                damping_spinbox.setDecimals(5)
                damping_spinbox.setSingleStep(0.00001)
                damping_spinbox.valueChanged.connect(
                    self.sig_manual_parameter_change.emit)
                damping_spinbox.valueChanged.connect(self.update_peak_average)
                self.tree.setItemWidget(channel_item, 2, damping_spinbox)
                amp_spinbox = QDoubleSpinBox()
                amp_spinbox.setRange(0, 9e99)
                amp_spinbox.valueChanged.connect(
                    self.sig_manual_parameter_change.emit)
                amp_spinbox.valueChanged.connect(self.update_peak_average)
                self.tree.setItemWidget(channel_item, 3, amp_spinbox)
                phase_spinbox = QDoubleSpinBox()
                phase_spinbox.setRange(-180, 180)
                phase_spinbox.setWrapping(True)
                phase_spinbox.valueChanged.connect(
                    self.sig_manual_parameter_change.emit)
                phase_spinbox.valueChanged.connect(self.update_peak_average)
                self.tree.setItemWidget(channel_item, 4, phase_spinbox)

        # Register that we've added another peak
        self.num_peaks += 1
        self.sig_add_new_peak.emit()

    def delete_selected(self):
        """Delete the item that is currently selected."""
        for i in range(self.tree.topLevelItemCount()):
            # If the radio button is checked
            peak_item = self.tree.topLevelItem(i)
            if peak_item is not None:
                if self.tree.itemWidget(peak_item, 5).isChecked():
                    # Delete this item
                    self.tree.takeTopLevelItem(i)
                    self.autofit_tree.takeTopLevelItem(i)
                    # self.num_peaks -= 1

    def toggle_view(self):
        if self.tree.isVisible():
            self.tree.hide()
            self.autofit_tree.show()
            self.reset_to_autofit_btn.show()
            self.view_autofit_btn.setText("View parameter values")
        else:
            self.tree.show()
            self.autofit_tree.hide()
            self.reset_to_autofit_btn.hide()
            self.view_autofit_btn.setText("View autofit parameters")

    def on_item_collapsed(self, item):
        index = self.sender().indexOfTopLevelItem(item)
        self.tree.collapseItem(self.tree.topLevelItem(index))
        self.autofit_tree.collapseItem(self.autofit_tree.topLevelItem(index))

    def on_item_expanded(self, item):
        index = self.sender().indexOfTopLevelItem(item)
        self.tree.expandItem(self.tree.topLevelItem(index))
        self.autofit_tree.expandItem(self.autofit_tree.topLevelItem(index))

    def on_selected_peak_changed(self, checked):
        for i in range(self.tree.topLevelItemCount()):
            # If the radio button in this row is the sender
            peak_item = self.tree.topLevelItem(i)
            if peak_item is not None:
                if self.tree.itemWidget(peak_item, 5) == self.sender():
                    if checked:
                        print("Selected peak: " + str(i))
                        self.sig_selected_peak_changed.emit(i)

    def reset_to_autofit(self):
        """Reset all parameters to be automatically adjusted."""
        for peak_number in range(self.num_peaks):
            peak_item = self.autofit_tree.topLevelItem(peak_number)

            for col in [1, 2, 3, 4]:
                self.autofit_tree.itemWidget(peak_item, col).setCurrentIndex(0)

            if len(self.channels) > 1:
                for channel_number in range(len(self.channels)):
                    channel_item = peak_item.child(channel_number)
                    for col in [1, 2, 3, 4]:
                        self.autofit_tree.itemWidget(channel_item,
                                                     col).setCurrentIndex(0)

        self.sig_recalculate_fit.emit()

    def update_peak_average(self):
        """Set the parameter values displayed for the peak to the average of
        all the channel values for each parameter."""
        for peak_number in range(self.num_peaks):
            # Get the peak item
            peak_item = self.tree.topLevelItem(peak_number)
            autofit_item = self.autofit_tree.topLevelItem(peak_number)

            if peak_item is not None:
                # Find the average values of all the channels
                avg_freq = 0
                avg_damping = 0
                avg_amplitude = 0
                avg_phase_deg = 0

                for channel_number in range(len(self.channels)):
                    avg_freq += self.get_frequency(peak_number, channel_number)
                    avg_damping += self.get_damping(peak_number,
                                                    channel_number)
                    avg_amplitude += self.get_amplitude(
                        peak_number, channel_number)
                    avg_phase_deg += self.get_phase_deg(
                        peak_number, channel_number)

                avg_freq /= len(self.channels)
                avg_damping /= len(self.channels)
                avg_amplitude /= len(self.channels)
                avg_phase_deg /= len(self.channels)

                # Set the peak item to display the averages
                if self.autofit_tree.itemWidget(autofit_item,
                                                1).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 1).setValue(avg_freq)
                if self.autofit_tree.itemWidget(autofit_item,
                                                2).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 2).setValue(avg_damping)
                if self.autofit_tree.itemWidget(autofit_item,
                                                3).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 3).setValue(avg_amplitude)
                if self.autofit_tree.itemWidget(autofit_item,
                                                4).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 4).setValue(avg_phase_deg)

    def get_frequency(self, peak_number, channel_number=None):
        """Return the resonant frequency (Hz) of the peak given by
        *peak_number*. If *channel_number* is given, return the resonant
        frequency of the given peak in the given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            spinbox = self.tree.itemWidget(channel_item, 1)
            return spinbox.value()
        else:
            return 0

    def get_omega(self, peak_number, channel_number=None):
        """Return the resonant frequency (rads) of the peak given by
        *peak_number*. If *channel_number* is given, return the resonant
        frequency of the given peak in the given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            spinbox = self.tree.itemWidget(channel_item, 1)
            return spinbox.value() * 2 * np.pi
        else:
            return 0

    def get_damping(self, peak_number, channel_number=None):
        """Return the damping ratio of the peak given by *peak_number*. If
        *channel_number* is given, return the damping ratio of the given peak
        in the given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            spinbox = self.tree.itemWidget(channel_item, 2)
            return spinbox.value()
        else:
            return 0

    def get_amplitude(self, peak_number, channel_number=None):
        """Return the amplitude of the peak given by *peak_number*. If
        *channel_number* is given, return the amplitude of the given peak in
        the given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            spinbox = self.tree.itemWidget(channel_item, 3)
            return spinbox.value()
        else:
            return 0

    def get_phase_deg(self, peak_number, channel_number=None):
        """Return the phase (deg) of the peak given by *peak_number*. If
        *channel_number* is given, return the phase of the given peak in the
        given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            spinbox = self.tree.itemWidget(channel_item, 4)
            return spinbox.value()
        else:
            return 0

    def get_phase_rad(self, peak_number, channel_number=None):
        """Return the phase (rad) of the peak given by *peak_number*. If
        *channel_number* is given, return the phase of the given peak in the
        given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            spinbox = self.tree.itemWidget(channel_item, 4)
            return np.deg2rad(spinbox.value())
        else:
            return 0

    def set_frequency(self, peak_number, channel_number=None, value=0):
        """Set the frequency (Hz) of a given peak to *value*. If *channel_number* is
        given, set the frequency of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            if autofit_peak_item.childCount():
                autofit_channel_item = autofit_peak_item.child(channel_number)
            else:
                autofit_channel_item = autofit_peak_item
            # Set the value only if the combobox is set to autofit
            combobox = \
                self.autofit_tree.itemWidget(autofit_channel_item, 1)
            if combobox.currentText() == "Auto":
                spinbox = self.tree.itemWidget(channel_item, 1)
                spinbox.setValue(value)

    def set_omega(self, peak_number, channel_number=None, value=0):
        """Set the frequency (rad) of a given peak to *value*. If *channel_number* is
        given, set the frequency of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            if autofit_peak_item.childCount():
                autofit_channel_item = autofit_peak_item.child(channel_number)
            else:
                autofit_channel_item = autofit_peak_item
            # Set the value only if the combobox is set to autofit
            combobox = \
                self.autofit_tree.itemWidget(autofit_channel_item, 1)
            if combobox.currentText() == "Auto":
                spinbox = self.tree.itemWidget(channel_item, 1)
                spinbox.setValue(value / (2 * np.pi))

    def set_damping(self, peak_number, channel_number=None, value=0):
        """Set the damping ratio of a given peak to *value*. If *channel_number* is
        given, set the damping ratio of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            if autofit_peak_item.childCount():
                autofit_channel_item = autofit_peak_item.child(channel_number)
            else:
                autofit_channel_item = autofit_peak_item
            # Set the value only if the combobox is set to autofit
            combobox = \
                self.autofit_tree.itemWidget(autofit_channel_item, 2)
            if combobox.currentText() == "Auto":
                spinbox = self.tree.itemWidget(channel_item, 2)
                spinbox.setValue(value)

    def set_amplitude(self, peak_number, channel_number=None, value=0):
        """Set the amplitude of a given peak to *value*. If *channel_number* is
        given, set the amplitude of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            if autofit_peak_item.childCount():
                autofit_channel_item = autofit_peak_item.child(channel_number)
            else:
                autofit_channel_item = autofit_peak_item
            # Set the value only if the combobox is set to autofit
            combobox = \
                self.autofit_tree.itemWidget(autofit_channel_item, 3)
            if combobox.currentText() == "Auto":
                spinbox = self.tree.itemWidget(channel_item, 3)
                spinbox.setValue(value)

    def set_phase_deg(self, peak_number, channel_number=None, value=0):
        """Set the phase of a given peak to *value*. If *channel_number* is
        given, set the phase of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            if autofit_peak_item.childCount():
                autofit_channel_item = autofit_peak_item.child(channel_number)
            else:
                autofit_channel_item = autofit_peak_item
            # Set the value only if the combobox is set to autofit
            combobox = \
                self.autofit_tree.itemWidget(autofit_channel_item, 4)
            if combobox.currentText() == "Auto":
                spinbox = self.tree.itemWidget(channel_item, 4)
                spinbox.setValue(value)

    def set_phase_rad(self, peak_number, channel_number=None, value=0):
        """Set the phase of a given peak to *value*. If *channel_number* is
        given, set the phase of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            if peak_item.childCount():
                channel_item = peak_item.child(channel_number)
            else:
                channel_item = peak_item
            if autofit_peak_item.childCount():
                autofit_channel_item = autofit_peak_item.child(channel_number)
            else:
                autofit_channel_item = autofit_peak_item
            # Set the value only if the combobox is set to autofit
            combobox = \
                self.autofit_tree.itemWidget(autofit_channel_item, 4)
            if combobox.currentText() == "Auto":
                spinbox = self.tree.itemWidget(channel_item, 4)
                spinbox.setValue(np.rad2deg(value))
Beispiel #6
0
class VectorQueryDockWidget(QDockWidget):
    """
    Dockable window that is a combined profile and ruler
    """
    queryClosed = pyqtSignal(QDockWidget, name='queryClosed')

    def __init__(self, parent):
        QDockWidget.__init__(self, "Vector Query", parent)

        # create a new widget that lives in the dock window
        self.dockWidget = QWidget()
        self.mainLayout = QVBoxLayout()

        self.toolBar = QToolBar(self.dockWidget)
        self.setupActions()
        self.setupToolbar()
        self.mainLayout.addWidget(self.toolBar)

        self.treeWidget = QTreeWidget(self)
        self.treeWidget.setColumnCount(2)
        self.treeWidget.setHeaderLabels(["Field", "Value"])
        self.mainLayout.addWidget(self.treeWidget)

        self.dockWidget.setLayout(self.mainLayout)

        # tell the dock window this is the widget to display
        self.setWidget(self.dockWidget)

        self.resize(400, 400)

    def setupActions(self):
        """
        Create the actions to be shown on the toolbar
        """
        self.followAction = QAction(self)
        self.followAction.setText("&Follow Vector Query Tool")
        self.followAction.setStatusTip("Follow Vector Query Tool")
        self.followAction.setIcon(QIcon(":/viewer/images/queryvector.png"))
        self.followAction.setCheckable(True)
        self.followAction.setChecked(True)

    def setupToolbar(self):
        """
        Add the actions to the toolbar
        """
        self.toolBar.addAction(self.followAction)

    def vectorLocationSelected(self, results, layer):
        """
        called in response to vectorLocationSelected signal
        """
        if not self.followAction.isChecked():
            return

        title = "Vector Query: %s" % layer.title
        self.setWindowTitle(title)

        self.treeWidget.clear()
        for result in results:
            item = QTreeWidgetItem(["Feature", ""])
            for key in sorted(result.keys()):
                child = QTreeWidgetItem([key, result[key]])
                item.addChild(child)
            self.treeWidget.addTopLevelItem(item)
            self.treeWidget.expandItem(item)

    def closeEvent(self, event):
        """
        Window is being closed - inform parent window
        """
        self.queryClosed.emit(self)
Beispiel #7
0
class CircleFitTree(QGroupBox):
    """
    The tree displaying the Circle Fit results and the parameters used for
    automatically fitting the circle.

    Attributes
    ----------
    sig_selected_peak_changed : pyqtSignal(int)
        The signal emitted when the selected peak is changed.
    """

    sig_selected_peak_changed = pyqtSignal(int)

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

        # TODO
        if channelset is None:
            self.num_channels = 5
        else:
            self.num_channels = len(channelset)

        self.num_peaks = 0

        self.init_ui()

    def init_ui(self):
        # # Tree for values
        self.tree = QTreeWidget()
        self.tree.setHeaderLabels([
            "Name", "Frequency", "Damping ratio", "Amplitude", "Phase",
            "Select"
        ])
        self.tree.setStyleSheet(
            "QTreeWidget::item:has-children { background-color : palette(mid);}"
        )

        # # Tree for autofit parameters
        self.autofit_tree = QTreeWidget()
        self.autofit_tree.setHeaderLabels([
            "Name", "Frequency", "Damping ratio", "Amplitude", "Phase",
            "Select"
        ])
        self.autofit_tree.hide()
        self.autofit_tree.setStyleSheet(
            "QTreeWidget::item:has-children { background-color : palette(mid);}"
        )

        # Connect the two trees together, so the views look identical
        self.autofit_tree.itemCollapsed.connect(self.on_item_collapsed)
        self.autofit_tree.itemExpanded.connect(self.on_item_expanded)
        self.tree.itemCollapsed.connect(self.on_item_collapsed)
        self.tree.itemExpanded.connect(self.on_item_expanded)

        # # Controls
        self.add_peak_btn = QPushButton("Add new peak", self)
        self.add_peak_btn.clicked.connect(self.add_peak)

        self.delete_selected_btn = QPushButton("Delete selected", self)
        self.delete_selected_btn.clicked.connect(self.delete_selected)

        self.view_autofit_btn = QPushButton("View autofit parameters", self)
        self.view_autofit_btn.clicked.connect(self.toggle_view)

        self.reset_to_autofit_btn = QPushButton("Reset all to auto", self)
        self.reset_to_autofit_btn.clicked.connect(self.reset_to_autofit)
        self.reset_to_autofit_btn.hide()

        controls1 = QGridLayout()
        controls1.addWidget(self.add_peak_btn, 0, 0)
        controls1.setColumnStretch(1, 1)
        controls1.addWidget(self.delete_selected_btn, 0, 2)

        controls2 = QGridLayout()
        controls2.setColumnStretch(0, 1)
        controls2.addWidget(self.reset_to_autofit_btn, 0, 1)
        controls2.addWidget(self.view_autofit_btn, 0, 2)

        # # Layout
        layout = QGridLayout()
        layout.addLayout(controls2, 0, 0)
        layout.addWidget(self.tree, 1, 0)
        layout.addWidget(self.autofit_tree, 1, 0)
        layout.addLayout(controls1, 2, 0)

        self.setLayout(layout)

    def add_peak(self):
        """Add a top-level item for a new peak to the tree, with children for
        each channel."""
        # Create the parent item for the peak
        peak_item = QTreeWidgetItem(
            self.tree, ["Peak {}".format(self.num_peaks), "", "", "", "", ""])
        autofit_peak_item = QTreeWidgetItem(
            self.autofit_tree,
            ["Peak {}".format(self.num_peaks), "", "", "", "", ""])

        # Put a radio button in column 5 in the tree
        radio_btn = QRadioButton()
        radio_btn.toggled.connect(self.on_selected_peak_changed)
        self.tree.setItemWidget(peak_item, 5, radio_btn)
        radio_btn.setChecked(True)

        for col in [1, 2, 3, 4]:
            # Put spinboxes in the tree
            spinbox = QDoubleSpinBox()
            self.tree.setItemWidget(peak_item, col, spinbox)
            # Put comboboxes in the autofit tree
            combobox = QComboBox()
            combobox.addItems(["Auto", "Manual"])
            combobox.currentIndexChanged.connect(self.update_peak_average)
            self.autofit_tree.setItemWidget(autofit_peak_item, col, combobox)

        # Create the child items for each channel for this peak if there's
        # more than one channel
        if self.num_channels > 1:
            for i in range(self.num_channels):
                channel_item = QTreeWidgetItem(
                    peak_item, ["Channel {}".format(i), "", "", "", "", ""])
                autofit_channel_item = QTreeWidgetItem(
                    autofit_peak_item,
                    ["Channel {}".format(i), "", "", "", "", ""])
                for col in [1, 2, 3, 4]:
                    # Put spinboxes in the tree
                    spinbox = QDoubleSpinBox()
                    spinbox.valueChanged.connect(self.update_peak_average)
                    self.tree.setItemWidget(channel_item, col, spinbox)
                    # Put comboboxes in the autofit tree
                    combobox = QComboBox()
                    combobox.addItems(["Auto", "Manual"])
                    combobox.currentIndexChanged.connect(
                        self.update_peak_average)
                    self.autofit_tree.setItemWidget(autofit_channel_item, col,
                                                    combobox)

        # Register that we've added another peak
        self.num_peaks += 1

    def delete_selected(self):
        """Delete the item that is currently selected."""
        for i in range(self.tree.topLevelItemCount()):
            # If the radio button is checked
            peak_item = self.tree.topLevelItem(i)
            if peak_item is not None:
                if self.tree.itemWidget(peak_item, 5).isChecked():
                    # Delete this item
                    self.tree.takeTopLevelItem(i)
                    self.autofit_tree.takeTopLevelItem(i)
                    #self.num_peaks -= 1

    def toggle_view(self):
        if self.tree.isVisible():
            self.tree.hide()
            self.autofit_tree.show()
            self.reset_to_autofit_btn.show()
            self.view_autofit_btn.setText("View parameter values")
        else:
            self.tree.show()
            self.autofit_tree.hide()
            self.reset_to_autofit_btn.hide()
            self.view_autofit_btn.setText("View autofit parameters")

    def on_item_collapsed(self, item):
        index = self.sender().indexOfTopLevelItem(item)
        self.tree.collapseItem(self.tree.topLevelItem(index))
        self.autofit_tree.collapseItem(self.autofit_tree.topLevelItem(index))

    def on_item_expanded(self, item):
        index = self.sender().indexOfTopLevelItem(item)
        self.tree.expandItem(self.tree.topLevelItem(index))
        self.autofit_tree.expandItem(self.autofit_tree.topLevelItem(index))

    def on_selected_peak_changed(self, checked):
        for i in range(self.tree.topLevelItemCount()):
            # If the radio button in this row is the sender
            peak_item = self.tree.topLevelItem(i)
            if peak_item is not None:
                if self.tree.itemWidget(peak_item, 5) == self.sender():
                    if checked:
                        print("Selected: " + str(i))
                        self.sig_selected_peak_changed.emit(i)

    def reset_to_autofit(self):
        """Reset all parameters to be automatically adjusted."""
        for peak_number in range(self.num_peaks):
            peak_item = self.autofit_tree.topLevelItem(peak_number)

            for col in [1, 2, 3, 4]:
                self.autofit_tree.itemWidget(peak_item, col).setCurrentIndex(0)

            if self.num_channels > 1:
                for channel_number in range(self.num_channels):
                    channel_item = peak_item.child(channel_number)
                    for col in [1, 2, 3, 4]:
                        self.autofit_tree.itemWidget(channel_item,
                                                     col).setCurrentIndex(0)

    def update_peak_average(self):
        """Set the parameter values displayed for the peak to the average of
        all the channel values for each parameter."""
        for peak_number in range(self.num_peaks):
            # Get the peak item
            peak_item = self.tree.topLevelItem(peak_number)
            autofit_item = self.autofit_tree.topLevelItem(peak_number)

            if peak_item is not None:
                # Find the average values of all the channels
                avg_frequency = 0
                avg_damping = 0
                avg_amplitude = 0
                avg_phase = 0

                for channel_number in range(self.num_channels):
                    avg_frequency += self.get_frequency(
                        peak_number, channel_number)
                    avg_damping += self.get_damping(peak_number,
                                                    channel_number)
                    avg_amplitude += self.get_amplitude(
                        peak_number, channel_number)
                    avg_phase += self.get_phase(peak_number, channel_number)

                avg_frequency /= self.num_channels
                avg_damping /= self.num_channels
                avg_amplitude /= self.num_channels
                avg_phase /= self.num_channels

                # Set the peak item to display the averages
                if self.autofit_tree.itemWidget(autofit_item,
                                                1).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 1).setValue(avg_frequency)
                if self.autofit_tree.itemWidget(autofit_item,
                                                2).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 2).setValue(avg_damping)
                if self.autofit_tree.itemWidget(autofit_item,
                                                3).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 3).setValue(avg_amplitude)
                if self.autofit_tree.itemWidget(autofit_item,
                                                4).currentText() == "Auto":
                    self.tree.itemWidget(peak_item, 4).setValue(avg_phase)

    def get_frequency(self, peak_number, channel_number=None):
        """Return the resonant frequency of the peak given by
        *peak_number*. If *channel_number* is given, return the resonant
        frequency of the given peak in the given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if channel_number is None:
                spinbox = self.tree.itemWidget(peak_item, 1)
                return spinbox.value()
            else:
                channel_item = peak_item.child(channel_number)
                spinbox = self.tree.itemWidget(channel_item, 1)
                return spinbox.value()
        else:
            return 0

    def get_damping(self, peak_number, channel_number=None):
        """Return the damping ratio of the peak given by *peak_number*. If
        *channel_number* is given, return the damping ratio of the given peak
        in the given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if channel_number is None:
                spinbox = self.tree.itemWidget(peak_item, 2)
                return spinbox.value()
            else:
                channel_item = peak_item.child(channel_number)
                spinbox = self.tree.itemWidget(channel_item, 2)
                return spinbox.value()
        else:
            return 0

    def get_amplitude(self, peak_number, channel_number=None):
        """Return the amplitude of the peak given by *peak_number*. If
        *channel_number* is given, return the amplitude of the given peak in
        the given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if channel_number is None:
                spinbox = self.tree.itemWidget(peak_item, 3)
                return spinbox.value()
            else:
                channel_item = peak_item.child(channel_number)
                spinbox = self.tree.itemWidget(channel_item, 3)
                return spinbox.value()
        else:
            return 0

    def get_phase(self, peak_number, channel_number=None):
        """Return the phase of the peak given by *peak_number*. If
        *channel_number* is given, return the phase of the given peak in the
        given channel."""
        peak_item = self.tree.topLevelItem(peak_number)
        if peak_item is not None:
            if channel_number is None:
                spinbox = self.tree.itemWidget(peak_item, 4)
                return spinbox.value()
            else:
                channel_item = peak_item.child(channel_number)
                spinbox = self.tree.itemWidget(channel_item, 4)
                return spinbox.value()
        else:
            return 0

    def get_parameter_values(self, peak_number, channel_number=None):
        """Returns a dict of the parameter values of a peak. If
        *channel_number* is given, return a dict of the parameter values of the
        peak for the given channel."""
        if channel_number is None:
            return {
                "frequency": self.get_frequency(peak_number),
                "damping": self.get_damping(peak_number),
                "amplitude": self.get_amplitude(peak_number),
                "phase": self.get_phase(peak_number)
            }
        else:
            return \
                {"frequency": self.get_frequency(peak_number, channel_number),
                 "damping": self.get_damping(peak_number, channel_number),
                 "amplitude": self.get_amplitude(peak_number, channel_number),
                 "phase": self.get_phase(peak_number, channel_number)}

    def set_frequency(self, peak_number, channel_number=None, value=0):
        """Set the frequency of a given peak to *value*. If *channel_number* is
        given, set the frequency of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            # If they have children
            if channel_number is not None:
                # Get the channel items
                channel_item = peak_item.child(channel_number)
                autofit_channel_item = autofit_peak_item.child(channel_number)
                # Set the value only if the combobox is set to autofit
                combobox = \
                    self.autofit_tree.itemWidget(autofit_channel_item, 1)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(channel_item, 1)
                    spinbox.setValue(value)
            else:
                # Set the value only if the combobox is set to autofit
                combobox = self.autofit_tree.itemWidget(autofit_peak_item, 1)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(peak_item, 1)
                    spinbox.setValue(value)

    def set_damping(self, peak_number, channel_number=None, value=0):
        """Set the damping ratio of a given peak to *value*. If *channel_number* is
        given, set the damping ratio of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            # If they have children
            if channel_number is not None:
                # Get the channel items
                channel_item = peak_item.child(channel_number)
                autofit_channel_item = autofit_peak_item.child(channel_number)
                # Set the value only if the combobox is set to autofit
                combobox = \
                    self.autofit_tree.itemWidget(autofit_channel_item, 2)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(channel_item, 2)
                    spinbox.setValue(value)
            else:
                # Set the value only if the combobox is set to autofit
                combobox = self.autofit_tree.itemWidget(autofit_peak_item, 2)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(peak_item, 2)
                    spinbox.setValue(value)

    def set_amplitude(self, peak_number, channel_number=None, value=0):
        """Set the amplitude of a given peak to *value*. If *channel_number* is
        given, set the amplitude of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            # If they have children
            if channel_number is not None:
                # Get the channel items
                channel_item = peak_item.child(channel_number)
                autofit_channel_item = autofit_peak_item.child(channel_number)
                # Set the value only if the combobox is set to autofit
                combobox = \
                    self.autofit_tree.itemWidget(autofit_channel_item, 3)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(channel_item, 3)
                    spinbox.setValue(value)
            else:
                # Set the value only if the combobox is set to autofit
                combobox = self.autofit_tree.itemWidget(autofit_peak_item, 3)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(peak_item, 3)
                    spinbox.setValue(value)

    def set_phase(self, peak_number, channel_number=None, value=0):
        """Set the phase of a given peak to *value*. If *channel_number* is
        given, set the phase of the given peak in the given channel."""
        # Get the top level items
        peak_item = self.tree.topLevelItem(peak_number)
        autofit_peak_item = self.autofit_tree.topLevelItem(peak_number)
        # Check they exist
        if peak_item is not None:
            # If they have children
            if channel_number is not None:
                # Get the channel items
                channel_item = peak_item.child(channel_number)
                autofit_channel_item = autofit_peak_item.child(channel_number)
                # Set the value only if the combobox is set to autofit
                combobox = \
                    self.autofit_tree.itemWidget(autofit_channel_item, 4)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(channel_item, 4)
                    spinbox.setValue(value)
            else:
                # Set the value only if the combobox is set to autofit
                combobox = self.autofit_tree.itemWidget(autofit_peak_item, 4)
                if combobox.currentText() == "Auto":
                    spinbox = self.tree.itemWidget(peak_item, 4)
                    spinbox.setValue(value)

    def set_parameter_values(self,
                             peak_number,
                             channel_number=None,
                             channel_values={}):
        """Set the parameter values for a given peak to those given in
        *channel_values*. If *channel_number* is given, set the values of the
        given peak in the given channel."""
        if "frequency" in channel_values.keys():
            self.set_frequency(peak_number, channel_number,
                               channel_values["frequency"])
        if "damping" in channel_values.keys():
            self.set_damping(peak_number, channel_number,
                             channel_values["damping"])
        if "amplitude" in channel_values.keys():
            self.set_amplitude(peak_number, channel_number,
                               channel_values["amplitude"])
        if "phase" in channel_values.keys():
            self.set_phase(peak_number, channel_number,
                           channel_values["phase"])
Beispiel #8
0
class JobQueue(ToolInstance):
    SESSION_ENDURING = False
    SESSION_SAVE = False

    NAME_COL = 0
    STATUS_COL = 1
    SERVER_COL = 2
    CHANGE_PRIORITY = 3
    KILL_COL = 4
    DEL_COL = 5
    BROWSE_COL = 6

    def __init__(self, session, name):
        super().__init__(session, name)

        self.display_name = "Job Queue"

        self.tool_window = MainToolWindow(self)

        if not self.session.seqcrow_job_manager.initialized:
            self.session.seqcrow_job_manager.init_queue()

        self._build_ui()

        self._job_queued = self.session.seqcrow_job_manager.triggers.add_handler(
            JOB_QUEUED, lambda *args: self.fill_tree(*args))
        self._job_started = self.session.seqcrow_job_manager.triggers.add_handler(
            JOB_STARTED, lambda *args: self.fill_tree(*args))
        self._job_finished = self.session.seqcrow_job_manager.triggers.add_handler(
            JOB_FINISHED, lambda *args: self.fill_tree(*args))

        self.fill_tree()

    def _build_ui(self):
        layout = QGridLayout()

        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)

        self.tree = QTreeWidget()
        self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.tree.setHeaderLabels([
            "name", "status", "server", "prioritize", "kill", "delete",
            "browse"
        ])
        self.tree.setUniformRowHeights(True)

        self.tree.setColumnWidth(0, 150)
        layout.addWidget(self.tree, 0, 0, 6, 1, Qt.AlignTop)

        row = 0

        pause_button = QPushButton(
            "pause new jobs"
            if not self.session.seqcrow_job_manager.paused else "resume jobs")
        pause_button.setCheckable(True)
        pause_button.clicked.connect(lambda check: pause_button.setText(
            "pause new jobs" if not check else "resume jobs"))
        pause_button.setChecked(self.session.seqcrow_job_manager.paused)
        pause_button.clicked.connect(self.pause_queue)
        layout.addWidget(pause_button, row, 1, 1, 1, Qt.AlignTop)

        row += 1

        open_button = QPushButton("open structure")
        open_button.clicked.connect(self.open_jobs)
        layout.addWidget(open_button, row, 1, 1, 1, Qt.AlignTop)

        row += 1

        log_button = QPushButton("log")
        log_button.clicked.connect(self.open_log)
        layout.addWidget(log_button, row, 1, 1, 1, Qt.AlignTop)

        row += 1

        output_button = QPushButton("raw output")
        output_button.clicked.connect(self.open_output)
        layout.addWidget(output_button, row, 1, 1, 1, Qt.AlignTop)

        row += 1

        refresh_button = QToolButton()
        refresh_button.setSizePolicy(QSizePolicy.Fixed,
                                     QSizePolicy.MinimumExpanding)
        refresh_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        refresh_button.setIcon(
            QIcon(refresh_button.style().standardIcon(
                QStyle.SP_BrowserReload)))
        refresh_button.setText('check jobs')
        refresh_button.clicked.connect(
            lambda *args: self.session.seqcrow_job_manager.triggers.
            activate_trigger(JOB_QUEUED, "refresh"))
        layout.addWidget(refresh_button, row, 1, 1, 1, Qt.AlignTop)

        row += 1

        for i in range(0, row - 1):
            layout.setRowStretch(i, 0)

        layout.setRowStretch(row - 1, 1)
        layout.setColumnStretch(0, 1)
        layout.setColumnStretch(1, 0)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)

    def fill_tree(self, trigger_name=None, trigger_job=None):
        item_stack = [self.tree.invisibleRootItem()]

        self.tree.clear()

        jobs = self.session.seqcrow_job_manager.jobs

        for job in jobs:
            name = job.name
            parent = item_stack[0]
            item = QTreeWidgetItem(parent)
            item_stack.append(item)

            item.setData(self.NAME_COL, Qt.DisplayRole, job)
            item.setText(self.NAME_COL, name)

            if isinstance(job, LocalJob):
                if job.killed:
                    item.setText(self.STATUS_COL, "killed")

                    del_job_widget = QWidget()
                    del_job_layout = QGridLayout(del_job_widget)
                    del_job = QPushButton()
                    del_job.clicked.connect(
                        lambda *args, job=job: self.remove_job(job))
                    del_job.setIcon(
                        QIcon(del_job_widget.style().standardIcon(
                            QStyle.SP_DialogDiscardButton)))
                    del_job.setFlat(True)
                    del_job_layout.addWidget(del_job, 0, 0, 1, 1,
                                             Qt.AlignHCenter)
                    del_job_layout.setColumnStretch(0, 1)
                    del_job_layout.setContentsMargins(0, 0, 0, 0)
                    self.tree.setItemWidget(item, self.DEL_COL, del_job_widget)

                elif job.isRunning():
                    if job in self.session.seqcrow_job_manager.unknown_status_jobs:
                        unk_widget = QWidget()
                        unk_layout = QGridLayout(unk_widget)
                        unk = QPushButton()
                        unk.setIcon(
                            QIcon(unk_widget.style().standardIcon(
                                QStyle.SP_MessageBoxQuestion)))
                        unk.setFlat(True)
                        unk.clicked.connect(lambda *args, job=job: self.
                                            show_ask_if_running(job))
                        unk_layout.addWidget(unk, 0, 0, 1, 1, Qt.AlignHCenter)
                        unk_layout.setColumnStretch(0, 1)
                        unk_layout.setContentsMargins(0, 0, 0, 0)
                        self.tree.setItemWidget(item, self.STATUS_COL,
                                                unk_widget)

                    else:
                        item.setText(self.STATUS_COL, "running")

                        kill_widget = QWidget()
                        kill_layout = QGridLayout(kill_widget)
                        kill = QPushButton()
                        kill.setIcon(
                            QIcon(kill_widget.style().standardIcon(
                                QStyle.SP_DialogCancelButton)))
                        kill.setFlat(True)
                        kill.clicked.connect(lambda *args, job=job: job.kill())
                        kill.clicked.connect(
                            lambda *args, session=self.session: session.
                            seqcrow_job_manager.triggers.activate_trigger(
                                JOB_QUEUED, "resume"))
                        kill_layout.addWidget(kill, 0, 0, 1, 1, Qt.AlignLeft)
                        kill_layout.setColumnStretch(0, 0)
                        kill_layout.setContentsMargins(0, 0, 0, 0)
                        self.tree.setItemWidget(item, self.KILL_COL,
                                                kill_widget)

                elif job.isFinished():
                    if not job.error:
                        item.setText(self.STATUS_COL, "finished")
                    else:
                        error_widget = QWidget()
                        error_layout = QGridLayout(error_widget)
                        error = QPushButton()
                        error.setIcon(
                            QIcon(error_widget.style().standardIcon(
                                QStyle.SP_MessageBoxWarning)))
                        error.setFlat(True)
                        error.setToolTip(
                            "job did not finish without errors or output file cannot be found"
                        )
                        error_layout.addWidget(error, 0, 0, 1, 1,
                                               Qt.AlignHCenter)
                        error_layout.setColumnStretch(0, 1)
                        error_layout.setContentsMargins(0, 0, 0, 0)
                        self.tree.setItemWidget(item, self.STATUS_COL,
                                                error_widget)

                    del_job_widget = QWidget()
                    del_job_layout = QGridLayout(del_job_widget)
                    del_job = QPushButton()
                    del_job.clicked.connect(
                        lambda *args, job=job: self.remove_job(job))
                    del_job.setIcon(
                        QIcon(del_job_widget.style().standardIcon(
                            QStyle.SP_DialogDiscardButton)))
                    del_job.setFlat(True)
                    del_job_layout.addWidget(del_job, 0, 0, 1, 1,
                                             Qt.AlignHCenter)
                    del_job_layout.setColumnStretch(0, 1)
                    del_job_layout.setContentsMargins(0, 0, 0, 0)
                    self.tree.setItemWidget(item, self.DEL_COL, del_job_widget)

                else:
                    item.setText(self.STATUS_COL, "queued")

                    priority_widget = QWidget()
                    priority_layout = QGridLayout(priority_widget)
                    inc_priority = QPushButton()
                    inc_priority.setIcon(
                        QIcon(priority_widget.style().standardIcon(
                            QStyle.SP_ArrowUp)))
                    inc_priority.setFlat(True)
                    inc_priority.clicked.connect(
                        lambda *args, job=job: self.session.seqcrow_job_manager
                        .increase_priotity(job))
                    priority_layout.addWidget(inc_priority, 0, 0, 1, 1,
                                              Qt.AlignRight)
                    dec_priority = QPushButton()
                    dec_priority.setIcon(
                        QIcon(priority_widget.style().standardIcon(
                            QStyle.SP_ArrowDown)))
                    dec_priority.setFlat(True)
                    dec_priority.clicked.connect(
                        lambda *args, job=job: self.session.seqcrow_job_manager
                        .decrease_priotity(job))
                    priority_layout.addWidget(dec_priority, 0, 1, 1, 1,
                                              Qt.AlignLeft)
                    priority_layout.setColumnStretch(0, 1)
                    priority_layout.setColumnStretch(1, 1)
                    priority_layout.setContentsMargins(0, 0, 0, 0)
                    self.tree.setItemWidget(item, self.CHANGE_PRIORITY,
                                            priority_widget)

                    kill_widget = QWidget()
                    kill_layout = QGridLayout(kill_widget)
                    kill = QPushButton()
                    kill.setIcon(
                        QIcon(kill_widget.style().standardIcon(
                            QStyle.SP_DialogCancelButton)))
                    kill.setFlat(True)
                    kill.clicked.connect(lambda *args, job=job: job.kill())
                    kill.clicked.connect(
                        lambda *args, session=self.session: session.
                        seqcrow_job_manager.triggers.activate_trigger(
                            JOB_QUEUED, "resume"))
                    kill_layout.addWidget(kill, 0, 0, 1, 1, Qt.AlignLeft)
                    kill_layout.setColumnStretch(0, 0)
                    kill_layout.setContentsMargins(0, 0, 0, 0)
                    self.tree.setItemWidget(item, self.KILL_COL, kill_widget)

                item.setText(self.SERVER_COL, "local")

                if job.scratch_dir and os.path.exists(job.scratch_dir):
                    browse_widget = QWidget()
                    browse_layout = QGridLayout(browse_widget)
                    browse = QPushButton()
                    browse.clicked.connect(
                        lambda *args, job=job: self.browse_local(job))
                    browse.setIcon(
                        QIcon(browse_widget.style().standardIcon(
                            QStyle.SP_DirOpenIcon)))
                    browse.setFlat(True)
                    browse_layout.addWidget(browse, 0, 0, 1, 1, Qt.AlignLeft)
                    browse_layout.setColumnStretch(0, 1)
                    browse_layout.setContentsMargins(0, 0, 0, 0)
                    self.tree.setItemWidget(item, self.BROWSE_COL,
                                            browse_widget)

            self.tree.expandItem(item)

        self.tree.resizeColumnToContents(self.STATUS_COL)
        self.tree.resizeColumnToContents(self.SERVER_COL)
        self.tree.resizeColumnToContents(self.CHANGE_PRIORITY)
        self.tree.resizeColumnToContents(self.KILL_COL)
        self.tree.resizeColumnToContents(self.DEL_COL)
        self.tree.resizeColumnToContents(self.BROWSE_COL)

    def pause_queue(self):
        self.session.seqcrow_job_manager.paused = not self.session.seqcrow_job_manager.paused
        self.session.seqcrow_job_manager.triggers.activate_trigger(
            JOB_QUEUED, "pauseunpause")

    def resume_queue(self):
        self.session.seqcrow_job_manager.paused = False
        self.session.seqcrow_job_manager.triggers.activate_trigger(
            JOB_QUEUED, "resume")

    def open_jobs(self):
        jobs = self.session.seqcrow_job_manager.jobs
        ndxs = list(set([item.row() for item in self.tree.selectedIndexes()]))
        for ndx in ndxs:
            job = jobs[ndx]
            if hasattr(job, "output_name"):
                run(job.session,
                    "open \"%s\" coordsets true" % job.output_name,
                    log=False)

    def open_log(self):
        jobs = self.session.seqcrow_job_manager.jobs
        ndxs = list(set([item.row() for item in self.tree.selectedIndexes()]))
        for ndx in ndxs:
            job = jobs[ndx]
            if hasattr(job, "scratch_dir") and os.path.exists(job.scratch_dir):
                self.tool_window.create_child_window("%s log" % job.name,
                                                     window_class=JobLog,
                                                     scr_dir=job.scratch_dir)

    def open_output(self):
        jobs = self.session.seqcrow_job_manager.jobs
        ndxs = list(set([item.row() for item in self.tree.selectedIndexes()]))
        for ndx in ndxs:
            job = jobs[ndx]
            if hasattr(job, "output_name"):
                self.tool_window.create_child_window("%s log" % job.name,
                                                     window_class=JobOutput,
                                                     file=job.output_name)

    def kill_running(self):
        jobs = self.session.seqcrow_job_manager.jobs
        ndxs = list(set([item.row() for item in self.tree.selectedIndexes()]))
        for ndx in ndxs:
            job = jobs[ndx]
            if not job.isFinished():
                job.kill()
                job.wait()

        self.session.seqcrow_job_manager.triggers.activate_trigger(
            JOB_QUEUED, "kill")

    def browse_local(self, job):
        #for some reason, this doesn't use the native file browser if i just do QFileDialog
        filename, _ = QFileDialog.getOpenFileName(
            directory=os.path.abspath(job.scratch_dir))

        if filename:
            run(self.session, "open \"%s\"" % filename)

    def remove_job(self, job):
        if isinstance(job, LocalJob):
            self.tree.takeTopLevelItem(
                self.session.seqcrow_job_manager.jobs.index(job))
            self.session.seqcrow_job_manager.local_jobs.remove(job)
            if hasattr(job, "scratch_dir") and os.path.exists(job.scratch_dir):
                yes = QMessageBox.question(self.tool_window.ui_area, \
                                            "Remove local files?", \
                                            "%s has been removed from the queue.\n" % (job.name) + \
                                            "Would you also like to move '%s' to the trash bin?" % job.scratch_dir, \
                                            QMessageBox.Yes | QMessageBox.No)

                if yes == QMessageBox.Yes:
                    send2trash(job.scratch_dir)

    def show_ask_if_running(self, job):
        if isinstance(job, LocalJob):
            yes = QMessageBox.question(self.tool_window.ui_area, \
                                       "Job status unknown", \
                                       "%s was running the last time ChimeraX was closed.\n" % (str(job)) + \
                                       "If the job is still running, it might compete with other jobs for you computer's resources, " + \
                                       "which could cause any running job to error out.\n\n" + \
                                       "Has this job finished running?", \
                                       QMessageBox.Yes | QMessageBox.No)

            if yes == QMessageBox.Yes:
                self.session.seqcrow_job_manager.unknown_status_jobs.remove(
                    job)
                job.isRunning = lambda *args, **kwargs: False
                job.isFinished = lambda *args, **kwargs: True

                self.session.seqcrow_job_manager.triggers.activate_trigger(
                    JOB_FINISHED, job)

    def delete(self):
        """overload delete"""
        self.session.seqcrow_job_manager.triggers.remove_handler(
            self._job_queued)
        self.session.seqcrow_job_manager.triggers.remove_handler(
            self._job_started)
        self.session.seqcrow_job_manager.triggers.remove_handler(
            self._job_finished)
        super().delete()

    def close(self):
        """overload close"""
        self.session.seqcrow_job_manager.triggers.remove_handler(
            self._job_queued)
        self.session.seqcrow_job_manager.triggers.remove_handler(
            self._job_started)
        self.session.seqcrow_job_manager.triggers.remove_handler(
            self._job_finished)
        super().close()
Beispiel #9
0
class ToolbarSettingsTool:

    help_url = "help:user/tools/toolbar.html#settings"

    def __init__(self, session, toolbar, tool_window):
        self.session = session
        self.toolbar = toolbar
        self.tool_window = tool_window
        self._build_ui()

    def _build_ui(self):
        from PyQt5.QtWidgets import (
            QLabel, QPushButton,
            QTreeWidget, QAbstractItemView,
            QGridLayout, QHBoxLayout,
        )
        from PyQt5.QtGui import QIcon
        from PyQt5.QtCore import Qt
        from .manager import fake_mouse_mode_bundle_info
        parent = self.tool_window.ui_area
        # main widgets
        self.instructions = QLabel(parent)
        self.other = QTreeWidget(parent)
        self.home = _HomeTab(parent, sources=[self.other])
        line = QHLine(parent)
        # widget layout:
        main_layout = QGridLayout()
        parent.setLayout(main_layout)
        main_layout.addWidget(self.instructions, 1, 1, 1, 2)
        main_layout.addWidget(self.home, 2, 1)
        main_layout.addWidget(self.other, 2, 2)
        mod_layout = QHBoxLayout()
        main_layout.addLayout(mod_layout, 3, 1, Qt.AlignCenter)
        main_layout.addWidget(line, 4, 1, 1, 2)
        bottom_layout = QHBoxLayout()
        main_layout.addLayout(bottom_layout, 5, 1, 1, 2, Qt.AlignRight)

        # TODO: group and ungroup buttons for drop downs
        new_section = QPushButton("New section", parent)
        new_section.setToolTip("Add another section to Home tab")
        new_section.clicked.connect(self.new_section)
        mod_layout.addWidget(new_section)
        remove = QPushButton("Remove", parent)
        remove.setToolTip("Remove selection items")
        remove.clicked.connect(self.remove)
        mod_layout.addWidget(remove)

        # bottom section
        save = QPushButton("Save", parent)
        save.setToolTip("Save current Home tab configuration")
        save.clicked.connect(self.save)
        bottom_layout.addWidget(save)
        reset = QPushButton("Reset", parent)
        reset.setToolTip("Reset Home tab to default configuration")
        reset.clicked.connect(self.reset)
        bottom_layout.addWidget(reset)
        restore = QPushButton("Restore", parent)
        restore.setToolTip("Restore previously saved Home tab configuration")
        restore.clicked.connect(self.restore)
        bottom_layout.addWidget(restore)
        help = QPushButton("Help", parent)
        help.setToolTip("Show Help")
        help.clicked.connect(self.help)
        bottom_layout.addWidget(help)

        # widget contents/customization:
        self.instructions.setWordWrap(True)
        self.instructions.setText(
            "To customize the Toolbar's Home tab, "
            "use drag and drop to either move buttons and sections around within the Home tab "
            "or to copy them from the Available Buttons.  "
            "Edit the text in the Home tab to change the displayed name.  "
            "Check sections to make them compact.  "
            "All changes are immediately shown in the actual toobar.  "
            "Currently, pulldown menus and mouse modes are unsupported.")

        self.build_home_tab()
        self.home.childDraggedAndDropped.connect(self.update)

        self.other.setColumnCount(1)
        self.other.setDragEnabled(True)
        self.other.setDropIndicatorShown(True)
        self.other.setDragDropMode(QAbstractItemView.DragOnly)
        self.other.setHeaderLabels(["Available Buttons"])
        other_flags = Qt.ItemIsEnabled | Qt.ItemIsDragEnabled | Qt.ItemIsSelectable
        toolbar = self.session.toolbar._toolbar  # internal manager data
        last_tab = None
        last_section = None
        tab_item = None
        section_item = None
        for (tab, section, compact, display_name, icon_path, description, bundle_info, name, kw) in _other_layout(self.session, toolbar, hide_hidden=False):
            if bundle_info == fake_mouse_mode_bundle_info:
                continue
            if tab != last_tab:
                last_tab = tab
                last_section = None
                tab_item = QTreeWidgetItem(self.other, [tab])
                tab_item.setData(0, ITEM_TYPE_ROLE, TAB_TYPE)
                tab_item.setFlags(Qt.ItemIsEnabled)
                self.other.expandItem(tab_item)
            if section != last_section:
                last_section = section
                section_item = QTreeWidgetItem(tab_item, [section])
                section_item.setData(0, ITEM_TYPE_ROLE, SECTION_TYPE)
                section_item.setFlags(other_flags)
                # Treat all available section as not compact
                # section_item.setCheckState(0, Qt.Checked if compact else Qt.Unchecked)
                self.other.expandItem(section_item)
            item = QTreeWidgetItem(section_item, [f"{display_name}"])
            item.setData(0, ITEM_TYPE_ROLE, BUTTON_TYPE)
            item.setData(0, LINK_ROLE, f"{bundle_info.name}:{name}")
            item.setFlags(other_flags)
            if icon_path is None:
                icon = None
            else:
                icon = QIcon(icon_path)
                item.setIcon(0, icon)
            item.setToolTip(0, description)

    def build_home_tab(self):
        # the following is very similar to code for toolbar layout
        from PyQt5.QtCore import Qt
        from PyQt5.QtGui import QIcon
        if self.home.topLevelItemCount() != 0:
            self.home.itemChanged.disconnect()
            self.home.clear()
        self.home.setHeaderLabels(["Home Tab"])
        last_section = None
        section_item = None
        for (section, compact, display_name, icon_path, description, link, bi, name, kw) in _home_layout(self.session, _settings.home_tab):
            if section != last_section:
                last_section = section
                section_item = QTreeWidgetItem(self.home, [section])
                section_item.setData(0, ITEM_TYPE_ROLE, SECTION_TYPE)
                section_item.setFlags(SECTION_FLAGS)
                section_item.setCheckState(0, Qt.Checked if compact else Qt.Unchecked)
                self.home.expandItem(section_item)
            item = QTreeWidgetItem(section_item, [f"{display_name}"])
            item.setData(0, ITEM_TYPE_ROLE, BUTTON_TYPE)
            item.setData(0, LINK_ROLE, link)
            item.setFlags(BUTTON_FLAGS)
            if icon_path is None:
                icon = None
            else:
                icon = QIcon(icon_path)
                item.setIcon(0, icon)
            item.setToolTip(0, description)
        if self.home.topLevelItemCount() != 0:
            self.home.itemChanged.connect(self.update)

    def update(self, *args):
        # check if text of current section item is a duplicate
        if args:
            item, column = args
        else:
            item = None
        if item and item.data(0, ITEM_TYPE_ROLE) == SECTION_TYPE:
            # make sure section name is unique
            from collections import Counter
            section_name = item.text(0)
            parent = item.parent()
            if parent is None:
                parent = self.home.invisibleRootItem()
            current_section_names = []
            for i in range(parent.childCount()):
                item_name = parent.child(i).text(0)
                current_section_names.append(item_name)
            current_sections = Counter(current_section_names)
            if current_sections[section_name] > 1:
                from itertools import chain, count
                for suffix in chain(("",), count(2)):
                    new_name = f"new {section_name}{suffix}"
                    if new_name not in current_sections:
                        item.setText(0, new_name)
                        break
        # propagate user changes to home tab
        from PyQt5.QtWidgets import QTreeWidgetItemIterator
        home_tab = []
        cur_section = []
        it = QTreeWidgetItemIterator(self.home)
        while it.value():
            item = it.value()
            it += 1
            item_type = item.data(0, ITEM_TYPE_ROLE)
            if item_type == BUTTON_TYPE:
                display_name = item.text(0)
                link = item.data(0, LINK_ROLE)
                # TODO: examine linked item to see if display_name is the same
                name = link.split(sep=':', maxsplit=1)[1]
                if name == display_name:
                    cur_section.append(link)
                else:
                    cur_section.append((link, display_name))
            elif item_type == SECTION_TYPE:
                name = item.text(0)
                cur_section = []
                if item.checkState(0):
                    home_tab.append(((name, True), cur_section))
                else:
                    home_tab.append((name, cur_section))
        _settings.home_tab = home_tab
        tb = get_toolbar_singleton(self.session, create=False)
        if tb:
            tb.build_home_tab()

    def update_from_settings(self):
        self.build_home_tab()
        tb = get_toolbar_singleton(self.session, create=False)
        if tb:
            tb.build_home_tab()

    def new_section(self):
        # add new section to home tab
        current_sections = set()
        for i in range(self.home.topLevelItemCount()):
            item_name = self.home.topLevelItem(i).text(0)
            current_sections.add(item_name)
        from itertools import chain, count
        for suffix in chain(("",), count(2)):
            new_name = f"new section{suffix}"
            if new_name not in current_sections:
                section_item = QTreeWidgetItem(self.home, [new_name])
                section_item.setData(0, ITEM_TYPE_ROLE, SECTION_TYPE)
                section_item.setFlags(SECTION_FLAGS)
                section_item.setCheckState(0, Qt.Unchecked)
                self.home.expandItem(section_item)
                self.home.scrollToItem(section_item)
                return

    def remove(self):
        # remove selected sections/buttons from home tab
        for si in self.home.selectedItems():
            parent = si.parent()
            if parent:
                parent.removeChild(si)
            else:
                self.home.invisibleRootItem().removeChild(si)
        self.update()

    def save(self):
        # save current configuration in preferences
        _settings.save()

    def reset(self):
        # reset current configuration in original defaults
        _settings.reset()
        self.update_from_settings()

    def restore(self):
        # restore current configuration from saved preferences
        _settings.restore()
        self.update_from_settings()

    def help(self):
        from chimerax.help_viewer import show_url
        show_url(self.session, self.help_url)
Beispiel #10
0
class ThermoGroup(QWidget):
    """widget used for the 'other' and 'reference' frames on the relative tab"""
    changes = Signal()

    def __init__(self, name, session, nrg_fr, thermo_co, size, *args,
                 **kwargs):
        super().__init__(*args, **kwargs)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.session = session
        self.nrg_fr = nrg_fr
        self.thermo_co = thermo_co

        layout = QGridLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setRowStretch(0, 1)

        frame = QGroupBox(name)
        frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        frame_layout = QGridLayout(frame)
        frame_layout.setContentsMargins(0, 0, 0, 0)
        frame_layout.setRowStretch(0, 1)

        self.tree = QTreeWidget()
        self.tree.setColumnCount(3)
        self.tree.setHeaderLabels(["energy", "frequencies", "remove"])
        self.tree.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.tree.setColumnWidth(0, size[0])
        self.tree.setColumnWidth(1, size[1])
        self.tree.resizeColumnToContents(2)

        root_item = self.tree.invisibleRootItem()
        plus = QTreeWidgetItem(root_item)
        plus_button = QPushButton("add molecule group")
        plus_button.setFlat(True)
        plus_button.clicked.connect(self.add_mol_group)
        plus_button2 = QPushButton("")
        plus_button2.setFlat(True)
        plus_button2.clicked.connect(self.add_mol_group)
        self.tree.setItemWidget(plus, 0, plus_button)
        self.tree.setItemWidget(plus, 1, plus_button2)
        self.tree.insertTopLevelItem(1, plus)

        self.add_mol_group()

        frame_layout.addWidget(self.tree)

        layout.addWidget(frame)

    def add_mol_group(self, *args):
        row = self.tree.topLevelItemCount()

        root_item = self.tree.invisibleRootItem()

        mol_group = QTreeWidgetItem(root_item)
        self.tree.insertTopLevelItem(row, mol_group)
        trash_button = QPushButton()
        trash_button.setFlat(True)

        trash_button.clicked.connect(
            lambda *args, parent=mol_group: self.remove_mol_group(parent))
        trash_button.setIcon(
            QIcon(self.style().standardIcon(QStyle.SP_DialogDiscardButton)))

        add_conf_button = QPushButton("add conformer")
        add_conf_button.setFlat(True)
        add_conf_button.clicked.connect(
            lambda *args, conf_group_widget=mol_group: self.add_conf_group(
                conf_group_widget))

        add_conf_button2 = QPushButton("")
        add_conf_button2.setFlat(True)
        add_conf_button2.clicked.connect(
            lambda *args, conf_group_widget=mol_group: self.add_conf_group(
                conf_group_widget))

        add_conf_child = QTreeWidgetItem(mol_group)
        self.tree.setItemWidget(add_conf_child, 0, add_conf_button)
        self.tree.setItemWidget(add_conf_child, 1, add_conf_button2)
        self.tree.setItemWidget(mol_group, 2, trash_button)

        mol_group.setText(0, "group %i" % row)

        mol_group.addChild(add_conf_child)
        self.add_conf_group(mol_group)

        self.tree.expandItem(mol_group)

        self.changes.emit()

    def add_conf_group(self, conf_group_widget):
        row = conf_group_widget.childCount()

        conformer_item = QTreeWidgetItem(conf_group_widget)
        conf_group_widget.insertChild(row, conformer_item)

        nrg_combobox = FilereaderComboBox(self.session, otherItems=['energy'])
        nrg_combobox.currentIndexChanged.connect(
            lambda *args: self.changes.emit())
        freq_combobox = FilereaderComboBox(self.session,
                                           otherItems=['frequency'])
        freq_combobox.currentIndexChanged.connect(
            lambda *args: self.changes.emit())

        trash_button = QPushButton()
        trash_button.setFlat(True)
        trash_button.clicked.connect(
            lambda *args, combobox=nrg_combobox: combobox.deleteLater())
        trash_button.clicked.connect(
            lambda *args, combobox=freq_combobox: combobox.deleteLater())
        trash_button.clicked.connect(lambda *args, child=conformer_item:
                                     conf_group_widget.removeChild(child))
        trash_button.clicked.connect(lambda *args: self.changes.emit())
        trash_button.setIcon(
            QIcon(self.style().standardIcon(QStyle.SP_DialogCancelButton)))

        self.tree.setItemWidget(conformer_item, 0, nrg_combobox)
        self.tree.setItemWidget(conformer_item, 1, freq_combobox)
        self.tree.setItemWidget(conformer_item, 2, trash_button)

        self.changes.emit()

    def compOutputs(self):
        out = []
        for mol_index in range(1, self.tree.topLevelItemCount()):
            out.append([])
            mol = self.tree.topLevelItem(mol_index)
            for conf_ndx in range(1, mol.childCount()):
                conf = mol.child(conf_ndx)
                fr = self.tree.itemWidget(conf, 1).currentData()
                if fr is None:
                    continue

                if fr not in self.thermo_co:
                    self.thermo_co[fr] = CompOutput(fr)
                out[-1].append(self.thermo_co[fr])

        return out

    def energies(self):
        out = []
        for mol_index in range(1, self.tree.topLevelItemCount()):
            out.append([])
            mol = self.tree.topLevelItem(mol_index)
            for conf_ndx in range(1, mol.childCount()):
                conf = mol.child(conf_ndx)
                fr = self.tree.itemWidget(conf, 0).currentData()
                if fr is None:
                    continue

                out[-1].append(fr.other['energy'])

        return out

    def remove_mol_group(self, parent):
        for conf_ndx in range(1, parent.childCount()):
            conf = parent.child(conf_ndx)
            self.tree.itemWidget(conf, 0).destroy()
            self.tree.itemWidget(conf, 1).destroy()

        ndx = self.tree.indexOfTopLevelItem(parent)
        self.tree.takeTopLevelItem(ndx)

        self.changes.emit()

    def deleteLater(self):
        for mol_index in range(1, self.tree.topLevelItemCount()):
            mol = self.tree.topLevelItem(mol_index)
            for conf_ndx in range(1, mol.childCount()):
                conf = mol.child(conf_ndx)
                self.tree.itemWidget(conf, 0).deleteLater()
                self.tree.itemWidget(conf, 1).deleteLater()

        super().deleteLater()
Beispiel #11
0
class SimpleWindow(OpenGLWindow):
    def __init__(self, format=DEFAULT_FORMAT, size=(700, 700)):
        super(SimpleWindow, self).__init__()

        self.setFormat(format)
        self.resize(*size)

        self.m_program = None
        self.m_frame = 0
        self.buffer = None
        # self.scene = scene
        # scene.window = self
        self.layers = []

        # background color
        self.clearcolor = (1, 1, 1, 1)

        # model paramenters
        self.m_translation = np.zeros(3, dtype=np.float32)

        # view parameters
        self.v_scale = 0.1
        self.v_cam_distance = 2
        self.v_translation = np.zeros(3, dtype=np.float32)
        self.v_rotation = q.quaternion()

        # projection parameters
        self.p_nclip = 0.1
        self.p_fclip = 100
        self.p_fov = 60
        self.p_ratio = 4.0 / 3.0

        self.last_mouse_pos = None

        # self.disableCentering = False

        self.layerWidget = QTreeWidget()
        self.layerWidget.headerItem().setHidden(True)
        self.layerWidget.setSelectionMode(QAbstractItemView.MultiSelection)
        self.layerWidget.itemClicked.connect(self.updateLayerVisibility)

        # self.layerDockWidget.show()

    def updateLayerVisibility(self, item, col):
        selected_names = [
            item.text(0) for item in self.layerWidget.selectedItems()
        ]
        for layer in self.layers:
            layer.is_visible = layer.name() in selected_names
            for painter in layer.painters:
                painter.is_visible = painter.name() in selected_names
        self.renderLater()

    def setBBox(self, bbox):
        self.bbox = bbox

    def setLayer(self, layer):
        assert (type(layer) is Layer)

        if layer in self.layers:
            self.unsetLayer(layer)

        self.layers.append(layer)
        item = QTreeWidgetItem([layer.name()], 0)
        layer.tree_item = item
        self.layerWidget.addTopLevelItem(item)
        item.setSelected(layer.is_visible)
        for painter in layer.painters:
            child_item = QTreeWidgetItem([painter.name()], 0)
            item.addChild(child_item)
            child_item.setSelected(painter.is_visible)
        self.layerWidget.expandItem(item)

    def unsetLayer(self, layer):
        self.layerWidget.blockSignals(True)
        index = self.layerWidget.indexOfTopLevelItem(layer.tree_item)
        self.layerWidget.takeTopLevelItem(index)
        self.layerWidget.blockSignals(False)
        try:
            self.layers.remove(layer)
        except KeyError:
            print('attemped to delete crap that doesnt exist')
            pass

    # def clearLayers(self):
    #     self.layerWidget.clear()

    def initialise(self):
        self.crosshair_painter = crosshairPainter()
        gl.glClearColor(*self.clearcolor)
        gl.glEnable(gl.GL_PROGRAM_POINT_SIZE)
        gl.glDepthMask(gl.GL_TRUE)
        gl.glEnable(gl.GL_DEPTH_TEST)
        gl.glDepthFunc(gl.GL_LESS)
        # gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
        # gl.glEnable(gl.GL_BLEND)
        print(gl.glGetIntegerv(gl.GL_MAX_VIEWPORT_DIMS))
        self.center()
        self.renderLater()

    def center(self, bbox=None):
        # if not self.disableCentering:
        if bbox is None:
            self.v_scale = 0.1
            self.v_translation = np.zeros(3)
        elif bbox.is_empty:
            self.v_scale = 0.1
            self.v_translation = np.zeros(3)
        else:
            w, h = self.width(), self.height()
            mi = min(w, h)
            self.v_scale = .8 * 2 * min((w / mi) / bbox.width[0],
                                        (h / mi) / bbox.width[1])
            self.v_translation = -bbox.center

    def setClearColor(self, color):
        self.clearcolor = color

    def render(self):
        # if self.scene.is_changed:
        #     self.center(self.scene.bbox)
        #     self.scene.is_changed = False

        gl.glViewport(0, 0, self.width(), self.height())

        gl.glClearColor(*self.clearcolor)

        gl.glClear(gl.GL_COLOR_BUFFER_BIT)

        bits = 0
        bits |= gl.GL_COLOR_BUFFER_BIT
        bits |= gl.GL_DEPTH_BUFFER_BIT
        bits |= gl.GL_STENCIL_BUFFER_BIT
        gl.glClear(bits)

        for layer in self.layers:
            if layer.is_visible:
                for painter in layer.painters:
                    if painter.is_visible:
                        painter.render(view=self)
        if self.crosshair_painter.is_visible:
            self.crosshair_painter.render()

        self.m_frame += 1

    def screen2view(self, x, y):
        w, h = self.width(), self.height()
        r = 2 * self.radius
        return (x - w / 2.) / r, ((h - y) - h / 2.) / r

    @property
    def radius(self):
        return 0.5 * min(self.width(), self.height())

    @property
    def mat_view(self):
        mat = np.eye(4, dtype=np.float32)
        translate(mat, *self.v_translation)
        scale(mat, self.v_scale, self.v_scale, self.v_scale)
        mat = mat.dot(np.array(q.matrix(self.v_rotation), dtype=np.float32))
        translate(mat, 0, 0, -self.v_cam_distance)
        return mat

    @property
    def mat_model(self):
        return translate(np.eye(4, dtype=np.float32), *self.m_translation)

    @property
    def mat_projection(self):
        return perspective(self.p_fov, self.p_ratio, self.p_nclip,
                           self.p_fclip)

    def computeMVPMatrix(self):
        mvp_matrix = np.eye(4, dtype=np.float32)
        # model
        translate(mvp_matrix, *self.m_translation)
        # view
        mvp_matrix = mvp_matrix.dot(self.mat_view)
        # projection
        projection = perspective(self.p_fov, self.p_ratio, self.p_nclip,
                                 self.p_fclip)
        return mvp_matrix.dot(projection)

    def resizeEvent(self, event):
        size = event.size()
        self.p_ratio = size.width() / size.height()
        self.renderNow()

    def wheelEvent(self, event):
        modifiers = event.modifiers()
        ticks = float(event.angleDelta().y() + event.angleDelta().x()) / 50
        if modifiers == Qt.ShiftModifier:
            # if self.projection_mode == 'perspective':
            old_fov = self.p_fov
            # do `dolly zooming` so that world appears at same size after canging fov
            self.p_fov = max(5., self.p_fov + ticks)
            self.p_fov = min(120., self.p_fov)
            self.v_cam_distance = self.v_cam_distance * (math.tan(
                math.radians(old_fov) / 2.)) / (math.tan(
                    math.radians(self.p_fov) / 2.))
        else:
            self.v_scale *= (ticks / 30 + 1.)
            self.v_scale = max(1E-3, self.v_scale)
            self.v_scale = min(1E3, self.v_scale)
        self.renderNow()

    def mouseMoveEvent(self, event):
        modifiers = event.modifiers()
        buttons = event.buttons()
        pos_x, pos_y = event.x(), event.y()

        if self.last_mouse_pos is None:
            self.last_mouse_pos = pos_x, pos_y

        if Qt.ShiftModifier == modifiers:
            x0, y0 = self.last_mouse_pos
            x1, y1 = pos_x, pos_y
            dx, dy = (x1 - x0), (y1 - y0)
            #scale to zero plane in projection frustrum
            scale = self.v_cam_distance * math.tan(
                math.radians(self.p_fov / 2.))
            dx, dy = scale * dx, scale * dy
            r = self.radius
            #multiply with inverse view matrix and apply translation in world coordinates
            self.v_translation += np.array([dx / r, -dy / r, 0., 0.]).dot(
                np.linalg.inv(self.mat_view))[:3]
            self.crosshair_painter.is_visible = True
        elif Qt.LeftButton == buttons:
            x0, y0 = self.screen2view(*self.last_mouse_pos)
            x1, y1 = self.screen2view(pos_x, pos_y)

            v0 = q.arcball(x0, y0)
            v1 = q.arcball(x1, y1)

            self.v_rotation = q.product(v1, v0, self.v_rotation)
            self.crosshair_painter.is_visible = True
        else:
            self.crosshair_painter.is_visible = False

        self.last_mouse_pos = pos_x, pos_y
        self.renderNow()

    def keyPressEvent(self, event):
        key = event.key()
        repeat = event.isAutoRepeat()
        if key == Qt.Key_T:
            self.v_rotation = q.quaternion()
        elif key == Qt.Key_U:
            if hasattr(self, 'bbox'):
                self.center(self.bbox)
        self.renderNow()