Exemple #1
0
class HarmTemplate(QWidget):
    def __init__(self, node, parent=None):
        super(HarmTemplate, self).__init__(parent)

        self.attrs = [
            "decay", "termination", "numWaves", "amplitude", "waveLength"
        ]
        self.lbl = QLabel("Wave:", self)
        self.wave = WaveDraw(self)
        self.layout = QVBoxLayout(self)
        self.layout.insertWidget(0, self.wave)
        self.layout.insertWidget(0, self.lbl)

        self.scriptJobs = None
        self._node = None
        self.setNode(node)

    def updateUI(self):
        ''' Load the UI values from the node '''
        vals = [cmds.getAttr(self._node + "." + a) for a in self.attrs]
        self.wave.updateValues(*vals)

    def _buildScriptJobs(self):
        self.scriptJobs = []
        for attr in self.attrs:
            name = '{0}.{1}'.format(self._node, attr)
            self.scriptJobs.append(
                cmds.scriptJob(ac=[name, self.updateUI], killWithScene=1))

    def _killScriptJobs(self):
        for sj in self.scriptJobs:
            for _ in range(10):
                try:
                    if cmds.scriptJob(exists=sj):
                        cmds.scriptJob(kill=sj, force=True)
                    break
                except RuntimeError:
                    #This happens very rarely when that scriptJob is
                    #being executed at the same time we try to kill it.
                    cmds.warning(
                        "Got RuntimeError trying to kill scriptjob...trying again"
                    )
                    time.sleep(0.1)
            else:
                cmds.warning("Killing scriptjob is taking too long...skipping")
        self.scriptJobs = None

    def setNode(self, node):
        ''' This widget should now represent the same attr on a different node. '''
        oldNode = self._node
        self._node = node
        self.updateUI()

        if self.scriptJobs is None:
            self._buildScriptJobs()

        elif oldNode != self._node:
            self._killScriptJobs()
            self._buildScriptJobs()
    def _build_ui(self):
        layout = QVBoxLayout()

        self.results = QTextBrowser()
        font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        self.results.setFont(font)
        layout.insertWidget(0, self.results, 1)

        self.ui_area.setLayout(layout)

        self.manage(None)
Exemple #3
0
class Deck(QVBoxLayout):
    def __init__(self, heading, color=True):
        super().__init__()
        logging.info(f'[Deck] {heading} init')
        self.addWidget(Heading(heading))
        self.use_color = color
        self.cards = []
        self.buttons = []
        self.heading = heading

        self.scroll_area = QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        self.addWidget(self.scroll_area)

        self.v_scroll = QVBoxLayout()
        self.v_scroll.setSpacing(SPACING)
        self.v_scroll.addStretch()

        self.scroll_widget = QWidget()
        self.scroll_widget.setLayout(self.v_scroll)
        self.scroll_area.setFixedWidth(WIDTH_WITH_SCROLL)
        self.scroll_area.setWidget(self.scroll_widget)

    def add_card_button(self, card):
        return self.insert_button_at_index(card, 0)

    def insert_button_at_index(self, card, index):
        button = CardButton(card)
        self.v_scroll.insertWidget(index, button)
        color = COLOR[card.color] if self.use_color else COLOR['gray']
        button.set_color(color)
        self.cards.insert(index, card.name)
        self.buttons.append(button)
        return button

    def remove_card_button(self, button):
        self.cards.remove(button.card.name)
        self.buttons.remove(button)
        self.removeWidget(button)
        button.deleteLater()

    def clear(self):
        logging.info(f'[Deck] clear {self.heading}')
        for button in self.buttons:
            button.deleteLater()
        self.cards.clear()
        self.buttons.clear()
    def addPortWidget(self, port: 'Port', layout: QVBoxLayout):
        """
		Add a single port editor to a specified layout. The layout provided should
		be the layout for either the input or output port editors.
		
		:param port: The port to create the editor widget for.
		:type port: Port
		:param layout: The layout to place the editor widget in.
		:type layout: QVBoxLayout
		:return: None
		:rtype: NoneType
		"""

        if layout == self.ui.outputLayout:
            pew = PortEditorWidget(port, allowOptional=False)
        else:
            pew = PortEditorWidget(port, allowOptional=True)
        layout.insertWidget(0, pew)
Exemple #5
0
    def __init__(self):
        QObject.__init__(self)

        # Initialize visual elements
        self.mainWindow = MainWindow()
        self.mainContent = MainContent()

        # Add Main Content ot Main Window
        layout = QVBoxLayout()
        layout.insertWidget(0, self.mainContent)
        self.mainWindow.ui.centralwidget.setLayout(layout)

        # Initialize the menu actions
        self.__initializeMenuActions()

        # Initialize the button and their signals
        self.__initializeButtons()

        # Initialize the table widget
        self.__initializeTableWidget()
    def _build_ui(self):
        """
        ui should have:
            * table with a list of available tests and show results after they are done
            * way to filter tests
            * button to run tests
        """
        layout = QVBoxLayout()

        # table to list test classes and the results
        self.table = QTableWidget()
        self.table.setColumnCount(2)
        self.table.setHorizontalHeaderLabels(["test", "result"])
        self.table.horizontalHeader().setSectionResizeMode(
            0,
            self.table.horizontalHeader().Interactive)
        self.table.setEditTriggers(QTableWidget.NoEditTriggers)
        self.table.horizontalHeader().setSectionResizeMode(
            1,
            self.table.horizontalHeader().Stretch)
        self.table.setSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.MinimumExpanding)
        self.table.setSelectionBehavior(self.table.SelectRows)
        layout.insertWidget(0, self.table, 1)

        self.filter = QLineEdit()
        self.filter.setPlaceholderText("filter test names")
        self.filter.setClearButtonEnabled(True)
        self.filter.textChanged.connect(self.apply_filter)
        layout.insertWidget(1, self.filter, 0)

        self.run_button = QPushButton("run tests")
        self.run_button.clicked.connect(self.run_tests)
        self.run_button.setToolTip(
            "if no tests are selected on the table, run all tests\n" +
            "otherwise, run selected tests")
        layout.insertWidget(2, self.run_button)

        self.fill_table()
        self.table.resizeColumnToContents(0)

        self.tool_window.ui_area.setLayout(layout)

        self.tool_window.manage(None)
Exemple #7
0
class GroupEcephys(QGroupBox):
    def __init__(self, parent):
        """Groupbox for Ecephys module fields filling form."""
        super().__init__()
        self.setTitle('Ecephys')
        self.group_type = 'Ecephys'
        self.groups_list = []

        self.combo1 = CustomComboBox()
        self.combo1.addItem('-- Add group --')
        self.combo1.addItem('Device')
        self.combo1.addItem('ElectrodeGroup')
        self.combo1.addItem('ElectricalSeries')
        self.combo1.addItem('SpikeEventSeries')
        self.combo1.addItem('EventDetection')
        self.combo1.addItem('EventWaveform')
        self.combo1.addItem('LFP')
        self.combo1.addItem('FilteredEphys')
        self.combo1.addItem('FeatureExtraction')
        self.combo1.addItem('DecompositionSeries')
        self.combo1.setCurrentIndex(0)
        self.combo1.activated.connect(lambda: self.add_group('combo'))
        self.combo2 = CustomComboBox()
        self.combo2.addItem('-- Del group --')
        self.combo2.setCurrentIndex(0)
        self.combo2.activated.connect(lambda: self.del_group('combo'))

        self.vbox1 = QVBoxLayout()
        self.vbox1.addStretch()

        self.grid = QGridLayout()
        self.grid.setColumnStretch(5, 1)
        if parent.show_add_del:
            self.grid.addWidget(self.combo1, 1, 0, 1, 2)
            self.grid.addWidget(self.combo2, 1, 2, 1, 2)
        self.grid.addLayout(self.vbox1, 2, 0, 1, 6)
        self.setLayout(self.grid)

    def add_group(self, group, metadata=None):
        """Adds group form."""
        if metadata is not None:
            group.write_fields(metadata=metadata)
        group.form_name.textChanged.connect(self.refresh_del_combo)
        self.groups_list.append(group)
        nWidgetsVbox = self.vbox1.count()
        self.vbox1.insertWidget(nWidgetsVbox - 1,
                                group)  # insert before the stretch
        self.combo1.setCurrentIndex(0)
        self.combo2.addItem(group.form_name.text())
        self.refresh_children(metadata=metadata)

    def del_group(self, group_name):
        """Deletes group form by name."""
        if group_name == 'combo':
            group_name = str(self.combo2.currentText())
        if group_name != '-- Del group --':
            # Tests if any other group references this one
            if self.is_referenced(grp_unique_name=group_name):
                QMessageBox.warning(
                    self, "Cannot delete subgroup", group_name +
                    " is being referenced by another subgroup(s).\n"
                    "You should remove any references of " + group_name +
                    " before "
                    "deleting it!")
                self.combo2.setCurrentIndex(0)
            else:
                nWidgetsVbox = self.vbox1.count()
                for i in range(nWidgetsVbox):
                    if self.vbox1.itemAt(i) is not None:
                        if hasattr(self.vbox1.itemAt(i).widget(), 'form_name'):
                            if self.vbox1.itemAt(
                                    i).widget().form_name.text() == group_name:
                                self.groups_list.remove(
                                    self.vbox1.itemAt(
                                        i).widget())  # deletes list item
                                self.vbox1.itemAt(i).widget().setParent(
                                    None)  # deletes widget
                                self.combo2.removeItem(
                                    self.combo2.findText(group_name))
                                self.combo2.setCurrentIndex(0)
                                self.refresh_children()

    def is_referenced(self, grp_unique_name):
        """Tests if a group is being referenced any other groups. Returns boolean."""
        nWidgetsVbox = self.vbox1.count()
        for i in range(nWidgetsVbox):
            if self.vbox1.itemAt(i).widget() is not None:
                other_grp = self.vbox1.itemAt(i).widget()
                # check if this subgroup has any ComboBox referencing grp_unique_name
                for ch in other_grp.children():
                    if isinstance(ch, (CustomComboBox, QComboBox)):
                        if ch.currentText() == grp_unique_name:
                            return True
        return False

    def refresh_children(self, metadata=None):
        """Refreshes references with existing objects in child groups."""
        for child in self.groups_list:
            child.refresh_objects_references(metadata=metadata)

    def refresh_del_combo(self):
        """Refreshes del combobox with existing objects names in child groups."""
        self.combo2.clear()
        self.combo2.addItem('-- Del group --')
        for child in self.groups_list:
            self.combo2.addItem(child.form_name.text())
        self.refresh_children()

    def read_fields(self):
        """Reads fields and returns them structured in a dictionary."""
        error = None
        data = {}
        # group_type counts, if there are multiple groups of same type, they are saved in a list
        grp_types = [grp.group_type for grp in self.groups_list]
        grp_type_count = {
            value: len(list(freq))
            for value, freq in groupby(sorted(grp_types))
        }
        # initiate lists as values for groups keys with count > 1
        for k, v in grp_type_count.items():
            if v > 1 or k == 'Device' or k == 'ElectrodeGroup' or k == 'ElectricalSeries':
                data[k] = []
        # iterate over existing groups and copy their metadata
        for grp in self.groups_list:
            if grp_type_count[grp.group_type] > 1 or grp.group_type == 'Device' \
               or grp.group_type == 'ElectrodeGroup' or grp.group_type == 'ElectricalSeries':
                data[grp.group_type].append(grp.read_fields())
            else:
                data[grp.group_type] = grp.read_fields()
        return data, error
Exemple #8
0
class FileSystemWidget(QWidget, DirectoryObserver):
    """
    Widget for listing directory contents and download files from the RDP client.
    """

    # fileDownloadRequested(file, targetPath, dialog)
    fileDownloadRequested = Signal(File, str, FileDownloadDialog)

    def __init__(self, root: Directory, parent: QObject = None):
        """
        :param root: root of all directories. Directories in root will be displayed with drive icons.
        :param parent: parent object.
        """

        super().__init__(parent)
        self.root = root
        self.breadcrumbLabel = QLabel()

        self.titleLabel = QLabel()
        self.titleLabel.setStyleSheet("font-weight: bold")

        self.titleSeparator: QFrame = QFrame()
        self.titleSeparator.setFrameShape(QFrame.HLine)

        self.listWidget = QListWidget()
        self.listWidget.setSortingEnabled(True)
        self.listWidget.setContextMenuPolicy(Qt.CustomContextMenu)
        self.listWidget.customContextMenuRequested.connect(self.onCustomContextMenu)

        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.addWidget(self.breadcrumbLabel)
        self.verticalLayout.addWidget(self.listWidget)

        self.setLayout(self.verticalLayout)
        self.listWidget.itemDoubleClicked.connect(self.onItemDoubleClicked)

        self.currentPath: Path = Path("/")
        self.currentDirectory: Directory = root
        self.listCurrentDirectory()

        self.currentDirectory.addObserver(self)

    def setWindowTitle(self, title: str):
        """
        Set the window title. When the title is not blank, a title label and a separator is displayed.
        :param title: the new title.
        """

        previousTitle = self.windowTitle()

        super().setWindowTitle(title)

        self.titleLabel.setText(title)

        if previousTitle == "" and title != "":
            self.verticalLayout.insertWidget(0, self.titleLabel)
            self.verticalLayout.insertWidget(1, self.titleSeparator)
        elif title == "" and previousTitle != "":
            self.verticalLayout.removeWidget(self.titleLabel)
            self.verticalLayout.removeWidget(self.titleSeparator)

            # noinspection PyTypeChecker
            self.titleLabel.setParent(None)

            # noinspection PyTypeChecker
            self.titleSeparator.setParent(None)

    def onItemDoubleClicked(self, item: FileSystemItem):
        """
        Handle double-clicks on items in the list. When the item is a directory, the current path changes and the
        contents of the directory are listed. Files are ignored.
        :param item: the item that was clicked.
        """

        if not item.isDirectory() and not item.isDrive():
            return

        if item.text() == "..":
            self.currentPath = self.currentPath.parent
        else:
            self.currentPath = self.currentPath / item.text()

        self.listCurrentDirectory()

    def listCurrentDirectory(self):
        """
        Refresh the list widget with the current directory's contents.
        """

        node = self.root

        for part in self.currentPath.parts[1 :]:
            node = next(d for d in node.directories if d.name == part)

        self.listWidget.clear()
        self.breadcrumbLabel.setText(f"Location: {str(self.currentPath)}")

        if node != self.root:
            self.listWidget.addItem(FileSystemItem("..", FileSystemItemType.Directory))

        for directory in node.directories:
            self.listWidget.addItem(FileSystemItem(directory.name, directory.type))

        for file in node.files:
            self.listWidget.addItem(FileSystemItem(file.name, file.type))

        if node is not self.currentDirectory:
            self.currentDirectory.removeObserver(self)
            node.addObserver(self)
            self.currentDirectory = node
            node.list()

    def onDirectoryChanged(self):
        """
        Refresh the directory view when the directory has changed.
        """

        self.listCurrentDirectory()

    def currentItemText(self) -> str:
        try:
            return self.listWidget.selectedItems()[0].text()
        except IndexError:
            return ""

    def selectedFile(self) -> Optional[File]:
        text = self.currentItemText()

        if text == "":
            return None

        if text == "..":
            return self.currentDirectory.parent

        for sequence in [self.currentDirectory.files, self.currentDirectory.directories]:
            for file in sequence:
                if text == file.name:
                    return file

        return None

    def canDownloadSelectedItem(self) -> bool:
        return self.selectedFile().type == FileSystemItemType.File

    def onCustomContextMenu(self, localPosition: QPoint):
        """
        Show a custom context menu with a "Download file" action when a file is right-clicked.
        :param localPosition: position where the user clicked.
        """
        selectedFile = self.selectedFile()

        if selectedFile is None:
            return

        globalPosition = self.listWidget.mapToGlobal(localPosition)

        downloadAction = QAction("Download file")
        downloadAction.setEnabled(selectedFile.type in [FileSystemItemType.File])
        downloadAction.triggered.connect(self.downloadFile)

        itemMenu = QMenu()
        itemMenu.addAction(downloadAction)

        itemMenu.exec_(globalPosition)

    def downloadFile(self):
        file = self.selectedFile()

        if file.type != FileSystemItemType.File:
            return

        filePath = file.getFullPath()
        targetPath, _ = QFileDialog.getSaveFileName(self, f"Download file {filePath}", file.name)

        if targetPath != "":
            dialog = FileDownloadDialog(filePath, targetPath, self)
            dialog.show()

            self.fileDownloadRequested.emit(file, targetPath, dialog)
Exemple #9
0
class main(QWidget):
    def __init__(self, parent=None):
        super(main, self).__init__(parent)
        self.setup()  # connections, widgets, layouts, etc.

        self.blksize = 2**20  # 1 MB; must be divisible by 16
        self.ext = '.enc'  # extension is appended to encrypted files
        self.path = ''
        self.encrypted = []  # to highlight done files in list
        self.decrypted = []

        self.clipboard = QApplication.clipboard()
        self.timeout = None  # to clear message label, see setMessage

        # this program was just an excuse to play with QprogressBar
        if not hash(os.urandom(11)) % 11:
            QTimer().singleShot(50, self.windDown)

        # various random hints
        hints = [
            'Freshly encrypted files can be renamed in the table!',
            'Clipboard is always cleared on program close!',
            'Keys can contain emoji if you <em>really</em> want: \U0001f4e6',
            'Keys can contain emoji if you <em>really</em> want: \U0001F511',
            'This isn\'t a tip, I just wanted to say hello!',
            'Keys can be anywhere from 8 to 4096 characters long!',
            'This program was just an excuse to play with the progress bars!',
            'Select \'Party\' in the hash button for progress bar fun!',
            ('Did you know you can donate one or all of your vital organs to '
             'the Aperture Science Self-Esteem Fund for Girls? It\'s true!'),
            ('It\'s been {:,} days since Half-Life 2: Episode '
             'Two'.format(int((time.time() - 1191988800) / 86400))),
            'I\'m version {}!'.format(VERSION),
            'I\'m version {}.whatever!'.format(VERSION.split('.')[0]),
            ('Brought to you by me, I\'m <a href="https://orthallelous.word'
             'press.com/">Orthallelous!</a>'),
            #'Brought to you by me, I\'m Htom Sirveaux!',
            'I wonder if there\'s beer on the sun',
            'Raspberry World: For all your raspberry needs. Off the beltline',
            #'I\'ve plummented to my death and I can\'t get up',
            '<em>NOT</em> compatible with the older version!',
            ('Hello there, fellow space travellers! Until somebody gives me '
             'some new lines in KAS, that is all I can say. - Bentusi Exchange'
             )
        ]
        if not hash(os.urandom(9)) % 4:
            self.extraLabel.setText(random.choice(hints))

    def genKey(self):
        "generate a random key"
        n = self.keySizeSB.value()
        char = string.printable.rstrip()  #map(chr, range(256))
        while len(char) < n:
            char += char
        key = ''.join(random.sample(char, n))
        self.keyInput.setText(key)

    def showKey(self, state=None):
        "hide/show key characters"
        if state is None: state = bool(self.showKeyCB.checkState())
        else: state = bool(state)
        if state: self.keyInput.setEchoMode(QLineEdit.Normal)
        else: self.keyInput.setEchoMode(QLineEdit.PasswordEchoOnEdit)

    def getFolder(self):
        "open file dialog and fill file table"
        path = QFileDialog(directory=self.path).getExistingDirectory()
        if not path: return
        self.path = str(path)
        self.populateTable(self.path)
        self.encrypted, self.decrypted = [], []
        return

    def resizeEvent(self, event):
        self.showFolder(self.path)  # update how the folder is shown

    def splitterChanged(self, pos):
        self.showFolder(self.path)  # likewise

    def showFolder(self, path):
        "displays current path, truncating as needed"
        if not path: return

        ell, sl = '\u2026', os.path.sep  # ellipsis, slash chars
        lfg, rfg = Qt.ElideLeft, Qt.ElideRight
        lst, wdh = os.path.basename(path), self.folderLabel.width()

        path = path.replace(os.path.altsep or '\\', sl)
        self.folderLabel.setToolTip(path)

        # truncate folder location
        fnt = QFontMetrics(self.folderLabel.font())
        txt = str(fnt.elidedText(path, lfg, wdh))

        if len(txt) <= 1:  # label is way too short
            self.folderLabel.setText('\u22ee' if txt != sl else txt)
            return  # but when would this happen?

        # truncate some more (don't show part of a folder name)
        if len(txt) < len(path) and txt[1] != sl:
            txt = ell + sl + txt.split(sl, 1)[-1]

            # don't truncate remaining folder name from the left
            if txt[2:] != lst and len(txt[2:]) < len(lst) + 2:
                txt = str(fnt.elidedText(ell + sl + lst, rfg, wdh))
        # you'd think len(txt) < len(lst) would work, but no; you'd be wrong

        self.folderLabel.setText(txt)

    def populateTable(self, path):
        "fill file table with file names"
        self.showFolder(path)

        names = []
        for n in os.listdir(path):
            if os.path.isdir(os.path.join(path, n)): continue  # folder
            names.append(n)

        self.folderTable.clearContents()
        self.folderTable.setRowCount(len(names))
        self.folderTable.setColumnCount(1)

        if not names:  # no files in this folder, inform user
            self.setMessage('This folder has no files')
            return

        self.folderTable.blockSignals(True)
        selEnab = Qt.ItemIsSelectable | Qt.ItemIsEnabled
        for i, n in enumerate(names):
            item = QTableWidgetItem()
            item.setText(n)
            item.setToolTip(n)
            item.setFlags(selEnab)

            # color code encrypted/decrypted files
            if n in self.encrypted:
                item.setTextColor(QColor(211, 70, 0))
                # allowed encrypted filenames to be changed
                item.setFlags(selEnab | Qt.ItemIsEditable)
            if n in self.decrypted:
                item.setForeground(QColor(0, 170, 255))
            self.folderTable.setItem(i, 0, item)
        if len(names) > 5:
            self.setMessage('{:,} files'.format(len(names)), 7)
        self.folderTable.blockSignals(False)
        return

    def editFileName(self, item):
        "change file name"
        new, old = str(item.text()), str(item.toolTip())

        result = QMessageBox.question(
            self, 'Renaming?',
            ("<p align='center'>Do you wish to rename<br>" +
             '<span style="color:#d34600;">{}</span>'.format(old) +
             "<br>to<br>" +
             '<span style="color:#ef4b00;">{}</span>'.format(new) +
             '<br>?</p>'))

        self.folderTable.blockSignals(True)
        if any(i in new for i in '/?<>:*|"^'):
            self.setMessage('Invalid character in name', 7)
            item.setText(old)
        elif result == QMessageBox.Yes:
            oold = os.path.join(self.path, old)
            try:
                os.rename(oold, os.path.join(self.path, new))
                self.encrypted.remove(old)
                self.encrypted.append(new)
                item.setToolTip(new)
            except Exception as err:
                self.setMessage(str(err), 9)
                item.setText(old)
                item.setToolTip(old)
                self.encrypted.remove(new)
                self.encrypted.append(old)
        else:
            item.setText(old)
        self.folderTable.blockSignals(False)

    def setMessage(self, message, secs=4, col=None):
        "show a message for a few seconds - col must be rgb triplet tuple"
        if self.timeout:  # https://stackoverflow.com/a/21081371
            self.timeout.stop()
            self.timeout.deleteLater()

        if col is None: color = 'rgb(255, 170, 127)'
        else:
            try:
                color = 'rgb({}, {}, {})'.format(*col)
            except:
                color = 'rgb(255, 170, 127)'

        self.messageLabel.setStyleSheet('background-color: {};'.format(color))
        self.messageLabel.setText(message)
        self.messageLabel.setToolTip(message)

        self.timeout = QTimer()
        self.timeout.timeout.connect(self.clearMessage)
        self.timeout.setSingleShot(True)
        self.timeout.start(secs * 1000)

    def clearMessage(self):
        self.messageLabel.setStyleSheet('')
        self.messageLabel.setToolTip('')
        self.messageLabel.setText('')

    def getName(self):
        "return file name of selected"
        items = self.folderTable.selectedItems()
        names = [str(i.text()) for i in items]
        if names: return names[0]  # only the first selected file
        else: return ''

    def showKeyLen(self, string):
        "displays a tooltip showing length of key"
        s = len(string)
        note = '{:,} character{}'.format(s, '' if s == 1 else 's')
        tip = QToolTip
        pos = self.genKeyButton.mapToGlobal(QPoint(0, 0))

        if s < self.minKeyLen:
            note = '<span style="color:#c80000;">{}</span>'.format(note)
        else:
            note = '<span style="color:#258f22;">{}</span>'.format(note)
        tip.showText(pos, note)

    def lock(self, flag=True):
        "locks buttons if True"
        stuff = [
            self.openButton,
            self.encryptButton,
            self.decryptButton,
            self.genKeyButton,
            self.hashButton,
            self.showKeyCB,
            self.copyButton,
            self.keyInput,
            self.keySizeSB,
            self.folderTable,
        ]
        for i in stuff:
            i.blockSignals(flag)
            i.setEnabled(not flag)
        return

    def _lerp(self, v1, v2, numPts=10):
        "linearly interpolate from v1 to v2\nFrom Orthallelous"
        if len(v1) != len(v2): raise ValueError("different dimensions")
        D, V, n = [], [], abs(numPts)
        for i, u in enumerate(v1):
            D.append(v2[i] - u)
        for i in range(n + 1):
            vn = []
            for j, u in enumerate(v1):
                vn.append(u + D[j] / float(n + 2) * i)
            V.append(tuple(vn))
        return V

    def weeeeeee(self):
        "party time"
        self.lock()
        self.setMessage('Party time!', 2.5)
        a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar
        process, sleep = app.processEvents, time.sleep

        am, bm, cm = a.minimum(), b.minimum(), c.minimum()
        ax, bx, cx = a.maximum(), b.maximum(), c.maximum()
        a.reset()
        b.reset()
        c.reset()

        loops = self._lerp((am, bm, cm), (ax, bx, cx), 100)
        ivops = loops[::-1]

        # up and up!
        for i in range(3):
            for j, k, l in loops:
                a.setValue(int(j))
                b.setValue(int(k))
                c.setValue(int(l))
                process()
                sleep(0.01)

        a.setValue(ax)
        b.setValue(bx)
        c.setValue(cx)
        sleep(0.25)
        a.setValue(am)
        b.setValue(bm)
        c.setValue(cm)

        # snake!
        self.setMessage('Snake time!')
        self.messageLabel.setStyleSheet('background-color: rgb(127,170,255);')
        for i in range(2):
            for j, k, l in loops:
                a.setValue(int(j))
                process()
                sleep(0.002)
            process()
            a.setInvertedAppearance(True)
            process()
            for j, k, l in ivops:
                a.setValue(int(j))
                process()
                sleep(0.002)

            for j, k, l in loops:
                b.setValue(int(k))
                process()
                sleep(0.002)
            process()
            b.setInvertedAppearance(False)
            process()
            for j, k, l in ivops:
                b.setValue(int(k))
                process()
                sleep(0.002)

            for j, k, l in loops:
                c.setValue(int(l))
                process()
                sleep(0.002)
            process()
            c.setInvertedAppearance(True)
            process()
            for j, k, l in ivops:
                c.setValue(int(l))
                process()
                sleep(0.002)

            process()
            b.setInvertedAppearance(True)
            process()
            for j, k, l in loops:
                b.setValue(int(k))
                process()
                sleep(0.002)
            process()
            b.setInvertedAppearance(False)
            process()
            for j, k, l in ivops:
                b.setValue(int(k))
                process()
                sleep(0.002)
            process()

            a.setInvertedAppearance(False)
            b.setInvertedAppearance(True)
            c.setInvertedAppearance(False)
        for j, k, l in loops:
            a.setValue(int(j))
            process()
            sleep(0.002)
        process()
        a.setInvertedAppearance(True)
        process()
        for j, k, l in ivops:
            a.setValue(int(j))
            process()
            sleep(0.002)

        # bars
        sleep(0.5)
        self.setMessage('Bars!')
        process()
        self.messageLabel.setStyleSheet('background-color: rgb(127,255,170);')
        for i in range(2):
            a.setValue(ax)
            time.sleep(0.65)
            a.setValue(am)
            sleep(0.25)
            process()
            b.setValue(bx)
            time.sleep(0.65)
            b.setValue(bm)
            sleep(0.25)
            process()
            c.setValue(cx)
            time.sleep(0.65)
            c.setValue(cm)
            sleep(0.25)
            process()
            b.setValue(bx)
            time.sleep(0.65)
            b.setValue(bm)
            sleep(0.25)
            process()

        # okay, enough
        process()
        a.setValue(ax)
        b.setValue(bx)
        c.setValue(cx)
        #a.setValue(am); b.setValue(bm); c.setValue(cm)
        a.setInvertedAppearance(False)
        b.setInvertedAppearance(True)
        c.setInvertedAppearance(False)
        self.lock(False)
        return

    def windDown(self, note=None):
        "silly deload on load"
        if note is None: note = 'Loading...'
        self.lock()
        self.setMessage(note)
        self.messageLabel.setStyleSheet('background-color: rgb(9, 190, 130);')
        a, b, c = self.encryptPbar, self.decryptPbar, self.hashPbar
        am, bm, cm = a.minimum(), b.minimum(), c.minimum()
        ax, bx, cx = a.maximum(), b.maximum(), c.maximum()
        a.reset()
        b.reset()
        c.reset()
        loops = self._lerp((ax, bx, cx), (am, bm, cm), 100)
        for j, k, l in loops:
            a.setValue(int(j))
            b.setValue(int(k))
            c.setValue(int(l))
            app.processEvents()
            time.sleep(0.02)
        a.reset()
        b.reset()
        c.reset()
        self.lock(False)
        self.clearMessage()

    def genHash(self, action):
        "generate hash of selected file and display it"
        name, t0 = self.getName(), time.perf_counter()

        # mark what hash was used in the drop-down menu
        for i in self.hashButton.menu().actions():
            if i == action: i.setIconVisibleInMenu(True)
            else: i.setIconVisibleInMenu(False)

        if str(action.text()) == 'Party':
            self.weeeeeee()
            self.windDown('Winding down...')
            return
        if not name:
            self.setMessage('No file selected')
            return
        if not os.path.exists(os.path.join(self.path, name)):
            self.setMessage('File does not exist')
            return

        self.lock()
        hsh = self.hashFile(os.path.join(self.path, name),
                            getattr(hashlib, str(action.text())))
        self.lock(False)
        #hsh = str(action.text()) + ': ' + hsh
        self.hashLabel.setText(hsh)
        self.hashLabel.setToolTip(hsh)
        self.extraLabel.setText(
            str(action.text()) + ' hash took ' +
            self.secs_fmt(time.perf_counter() - t0))

    def setCancel(self):
        "cancel operation"
        self._requestStop = True

    def showCancelButton(self, state=False):
        "show/hide cancel button"
        self.cancelButton.blockSignals(not state)
        self.cancelButton.setEnabled(state)
        if state:
            self.cancelButton.show()
            self.keyInput.hide()
            self.genKeyButton.hide()
            self.keySizeSB.hide()
        else:
            self.cancelButton.hide()
            self.keyInput.show()
            self.genKeyButton.show()
            self.keySizeSB.show()

    def hashFile(self, fn, hasher):
        "returns the hash value of a file"
        hsh, blksize = hasher(), self.blksize
        fsz, csz = os.path.getsize(fn), 0.0

        self.hashPbar.reset()
        self.showCancelButton(True)
        prog, title = '(# {:.02%}) {}', self.windowTitle()
        with open(fn, 'rb') as f:
            while 1:
                blk = f.read(blksize)
                if not blk: break
                hsh.update(blk)

                csz += blksize
                self.hashPbar.setValue(int(round(csz * 100.0 / fsz)))
                app.processEvents()
                self.setWindowTitle(prog.format(csz / fsz, title))
                if self._requestStop: break

        self.hashPbar.setValue(self.hashPbar.maximum())
        self.setWindowTitle(title)
        self.showCancelButton(False)

        if self._requestStop:
            self.setMessage('Hashing canceled!')
            self.hashPbar.setValue(self.hashPbar.minimum())
            self._requestStop = False
            return
        return hsh.hexdigest()

    def hashKey(self, key, salt=b''):
        "hashes a key for encrypting/decrypting file"
        salt = salt.encode() if type(salt) != bytes else salt
        key = key.encode() if type(key) != bytes else key
        p = app.processEvents
        self.setMessage('Key Hashing...', col=(226, 182, 249))
        p()
        key = hashlib.pbkdf2_hmac('sha512', key, salt, 444401)
        p()
        self.clearMessage()
        p()
        return hashlib.sha3_256(key).digest()  # AES requires a 32 char key

    def encrypt(self):
        "encrypt selected file with key"
        name, t0 = self.getName(), time.perf_counter()
        if not name:
            self.setMessage('No file selected')
            return
        if not os.path.exists(os.path.join(self.path, name)):
            self.setMessage('File does not exist')
            return
        key = str(self.keyInput.text())
        if len(key) < self.minKeyLen:
            self.setMessage(('Key must be at least '
                             '{} characters long').format(self.minKeyLen))
            return

        self.lock()
        gn = self.encryptFile(key, os.path.join(self.path, name))
        if not gn:
            self.lock(False)
            return
        self.encrypted.append(os.path.basename(gn))
        self.lock(False)

        self.populateTable(self.path)  # repopulate folder list
        bn, tt = os.path.basename(gn), time.perf_counter() - t0
        self.setMessage('Encrypted, saved "{}"'.format(bn, 13))
        self.extraLabel.setText('Encrypting took ' + self.secs_fmt(tt))

    def encryptFile(self, key, fn):
        "encrypts a file using AES (MODE_GCM)"
        chars = ''.join(map(chr, range(256))).encode()
        chk = AES.block_size
        sample = random.sample
        iv = bytes(sample(chars, chk * 2))
        salt = bytes(sample(chars * 2, 256))

        vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv)
        fsz = os.path.getsize(fn)
        del key
        blksize = self.blksize
        gn = fn + self.ext

        fne = os.path.basename(fn).encode()
        fnz = len(fne)
        if len(fne) % chk: fne += bytes(sample(chars, chk - len(fne) % chk))

        csz = 0.0  # current processed value
        self.encryptPbar.reset()
        prog, title = '({:.02%}) {}', self.windowTitle()
        self.showCancelButton(True)

        with open(fn, 'rb') as src, open(gn, 'wb') as dst:
            dst.write(bytes([0] * 16))  # spacer for MAC written at end
            dst.write(iv)
            dst.write(salt)  # store iv, salt
            # is it safe to store MAC, iv, salt plain right in file?
            # can't really store them encrypted,
            # or elsewhere in this model of single file encryption?
            # can't have another file for the file to lug around

            # store file size, file name length
            dst.write(vault.encrypt(struct.pack('<2Q', fsz, fnz)))
            dst.write(vault.encrypt(fne))  # store filename

            while 1:
                dat = src.read(blksize)
                if not dat: break
                elif len(dat) % chk:  # add padding
                    fil = chk - len(dat) % chk
                    dat += bytes(sample(chars, fil))
                dst.write(vault.encrypt(dat))

                csz += blksize  # show progress
                self.encryptPbar.setValue(int(round(csz * 100.0 / fsz)))
                self.setWindowTitle(prog.format(csz / fsz, title))
                app.processEvents()

                if self._requestStop: break
            if not self._requestStop:
                stuf = random.randrange(23)  # pack in more stuffing
                fing = b''.join(bytes(sample(chars, 16)) for i in range(stuf))
                dst.write(vault.encrypt(fing))  # and for annoyance

                dst.seek(0)
                dst.write(vault.digest())  # write MAC
                self.hashLabel.setText('MAC: ' + vault.hexdigest())

        self.encryptPbar.setValue(self.encryptPbar.maximum())
        self.setWindowTitle(title)
        self.showCancelButton(False)

        if self._requestStop:
            self.setMessage('Encryption canceled!')
            self.encryptPbar.setValue(self.encryptPbar.minimum())
            self._requestStop = False
            os.remove(gn)
            return
        return gn

    def decrypt(self):
        "encrypt selected file with key"
        name, t0 = self.getName(), time.perf_counter()
        if not name:
            self.setMessage('No file selected')
            return
        if not os.path.exists(os.path.join(self.path, name)):
            self.setMessage('File does not exist')
            return
        key = str(self.keyInput.text())
        if len(key) < self.minKeyLen:
            self.setMessage(('Key must be at least '
                             '{} characters long').format(self.minKeyLen))
            return

        self.lock()
        gn = self.decryptFile(key, os.path.join(self.path, name))
        if not gn:
            self.lock(False)
            return
        self.decrypted.append(os.path.basename(gn))
        self.lock(False)

        self.populateTable(self.path)  # repopulate folder list
        bn, tt = os.path.basename(gn), time.perf_counter() - t0
        self.setMessage('Decrypted, saved "{}"'.format(bn, 13))
        self.extraLabel.setText('Decrypting took ' + self.secs_fmt(tt))

    def decryptFile(self, key, fn):
        "decrypts a file using AES (MODE_GCM)"
        blksize = self.blksize
        gn = hashlib.md5(os.path.basename(fn).encode()).hexdigest()
        gn = os.path.join(self.path, gn)  # temporary name
        if os.path.exists(gn):
            self.setMessage('file already exists')
            return

        self.decryptPbar.reset()
        csz = 0.0  # current processed value
        chk, fnsz = AES.block_size, os.path.getsize(fn)
        prog, title = '({:.02%}) {}', self.windowTitle()
        try:
            with open(fn, 'rb') as src, open(gn, 'wb') as dst:
                # extract iv, salt
                MAC = src.read(16)
                iv = src.read(AES.block_size * 2)
                salt = src.read(256)
                vault = AES.new(self.hashKey(key, salt), AES.MODE_GCM, iv)
                self.showCancelButton(True)

                # extract file size, file name length
                sizes = src.read(struct.calcsize('<2Q'))
                fsz, fnz = struct.unpack('<2Q', vault.decrypt(sizes))

                # extract filename; round up fnz to nearest chk
                rnz = fnz if not fnz % chk else fnz + chk - fnz % chk
                rfn = vault.decrypt(src.read(rnz))[:fnz].decode()
                self.setMessage('Found "{}"'.format(rfn), 13, (255, 211, 127))

                while 1:
                    dat = src.read(blksize)
                    if not dat: break
                    dst.write(vault.decrypt(dat))

                    csz += blksize  # show progress
                    self.decryptPbar.setValue(int(round(csz * 100.0 / fnsz)))
                    self.setWindowTitle(prog.format(1 - (csz / fnsz), title))
                    app.processEvents()
                    if self._requestStop: break

                if not self._requestStop: dst.truncate(fsz)  # remove padding
            if not self._requestStop:
                vault.verify(MAC)
                self.hashLabel.setText('')

        except (ValueError, KeyError) as err:
            os.remove(gn)
            self.setMessage('Invalid decryption!')
            self.setWindowTitle(title)
            self.showCancelButton(False)
            return
        except Exception as err:
            os.remove(gn)
            self.setMessage('Invalid key or file!')
            self.setWindowTitle(title)
            self.showCancelButton(False)
            return
        self.decryptPbar.setValue(self.decryptPbar.maximum())
        self.setWindowTitle(title)
        self.showCancelButton(False)

        if self._requestStop:
            self.setMessage('Decryption canceled!')
            self.decryptPbar.setValue(self.decryptPbar.minimum())
            self._requestStop = False
            os.remove(gn)
            return

        # restore original file name
        name, ext = os.path.splitext(rfn)
        count = 1
        fn = os.path.join(self.path, name + ext)
        while os.path.exists(fn):
            fn = os.path.join(self.path, name + '_{}'.format(count) + ext)
            count += 1
        os.rename(gn, fn)  # restore original name
        return fn  # saved name

    def copyKeyHash(self, action):
        "copies either the key or the hash to clipboard"
        act = str(action.text()).lower()

        if 'key' in act: txt = str(self.keyInput.text())
        elif 'hash' in act: txt = str(self.hashLabel.text())
        else:
            self.setMessage('Invalid copy selection')
            return

        if not txt:
            self.setMessage('Empty text; Nothing to copy')
            return

        if 'key' in act: self.setMessage('Key copied to clipboard')
        elif 'hash' in act: self.setMessage('Hash copied to clipboard')
        else:
            self.setMessage('Invalid copy selection')
            return

        self.clipboard.clear()
        self.clipboard.setText(txt)

    def secs_fmt(self, s):
        "6357 -> '1h 45m 57s'"
        Y, D, H, M = 31556952, 86400, 3600, 60
        y = int(s // Y)
        s -= y * Y
        d = int(s // D)
        s -= d * D
        h = int(s // H)
        s -= h * H
        m = int(s // M)
        s -= m * M

        r = (str(int(s)) if int(s) == s else str(round(s, 3))) + 's'

        if m: r = str(m) + 'm ' + r
        if h: r = str(h) + 'h ' + r
        if d: r = str(d) + 'd ' + r
        if y: r = str(y) + 'y ' + r
        return r.strip()

    def closeEvent(self, event):
        self.clipboard.clear()

    def setup(self):
        "constructs the gui"
        Fixed = QSizePolicy()
        MinimumExpanding = QSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.MinimumExpanding)
        self.minKeyLen = 8
        self.maxKeyLen = 4096

        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Horizontal)
        self.splitter.splitterMoved.connect(self.splitterChanged)

        # left column
        self.leftColumn = QWidget()
        self.vl01 = QVBoxLayout()

        # left column - first item (0; horizonal layout 0)
        self.hl00 = QHBoxLayout()
        self.hl00.setSpacing(5)

        self.openButton = QPushButton('&Open')
        self.openButton.setToolTip('Open folder')
        self.openButton.setMinimumSize(60, 20)
        self.openButton.setMaximumSize(60, 20)
        self.openButton.setSizePolicy(Fixed)
        self.openButton.clicked.connect(self.getFolder)
        #ico = self.style().standardIcon(QStyle.SP_DirIcon)
        #self.openButton.setIcon(ico)

        self.folderLabel = QLabel()
        self.folderLabel.setMinimumSize(135, 20)
        self.folderLabel.setMaximumSize(16777215, 20)
        self.folderLabel.setSizePolicy(MinimumExpanding)
        self.hl00.insertWidget(0, self.openButton)
        self.hl00.insertWidget(1, self.folderLabel)

        # left column - second item (1)
        self.folderTable = QTableWidget()
        self.folderTable.setMinimumSize(200, 32)
        self.folderTable.horizontalHeader().setVisible(False)
        self.folderTable.horizontalHeader().setStretchLastSection(True)
        self.folderTable.verticalHeader().setVisible(False)
        self.folderTable.verticalHeader().setDefaultSectionSize(15)
        self.folderTable.itemChanged.connect(self.editFileName)

        # left column - third item (2)
        self.extraLabel = QLabel()
        self.extraLabel.setMinimumSize(200, 20)
        self.extraLabel.setMaximumSize(16777215, 20)
        self.extraLabel.setSizePolicy(MinimumExpanding)
        self.extraLabel.setTextInteractionFlags(Qt.LinksAccessibleByMouse)

        # finalize left column
        self.vl01.insertLayout(0, self.hl00)
        self.vl01.insertWidget(1, self.folderTable)
        self.vl01.insertWidget(2, self.extraLabel)
        self.leftColumn.setLayout(self.vl01)

        # right column
        self.rightColumn = QWidget()
        self.vl02 = QVBoxLayout()

        # right column - first item (0)
        self.messageLabel = QLabel()
        self.messageLabel.setMinimumSize(290, 20)
        self.messageLabel.setMaximumSize(16777215, 20)
        self.messageLabel.setSizePolicy(MinimumExpanding)
        self.messageLabel.setAlignment(Qt.AlignCenter)

        # right column - second item (2; horizontal layout 1)
        self.hl01 = QHBoxLayout()
        self.hl01.setSpacing(5)

        self.encryptButton = QPushButton('&Encrypt')  #\U0001F512
        self.encryptButton.setToolTip('Encrypt selected file')
        self.encryptButton.setMinimumSize(60, 20)
        self.encryptButton.setMaximumSize(60, 20)
        self.encryptButton.setSizePolicy(Fixed)
        self.encryptButton.clicked.connect(self.encrypt)

        self.encryptPbar = QProgressBar()
        self.encryptPbar.setMinimumSize(225, 20)
        self.encryptPbar.setMaximumSize(16777215, 20)
        self.encryptPbar.setSizePolicy(MinimumExpanding)
        self.encryptPbar.setTextVisible(False)

        palette = self.encryptPbar.palette()  # color of progress bar
        color = QColor(211, 70, 0)
        palette.setColor(QPalette.Highlight, color)
        self.encryptPbar.setPalette(palette)

        self.hl01.insertWidget(0, self.encryptButton)
        self.hl01.insertWidget(1, self.encryptPbar)

        # right column - third item (3; horizontal layout 2)
        self.hl02 = QHBoxLayout()
        self.hl02.setSpacing(5)

        self.cancelButton = QPushButton('C&ANCEL')
        self.cancelButton.setToolTip('Cancels current operation')
        self.cancelButton.setMinimumSize(70, 24)
        self.cancelButton.setMaximumSize(70, 24)
        self.cancelButton.setSizePolicy(Fixed)
        self.cancelButton.clicked.connect(self.setCancel)
        font = self.cancelButton.font()
        font.setBold(True)
        self.cancelButton.setFont(font)
        self.cancelButton.blockSignals(True)
        self.cancelButton.setEnabled(False)
        self.cancelButton.hide()
        self._requestStop = False

        self.keyInput = QLineEdit()
        self.keyInput.setMinimumSize(225, 20)
        self.keyInput.setMaximumSize(16777215, 20)
        self.keyInput.setSizePolicy(MinimumExpanding)
        self.keyInput.setPlaceholderText('key')
        self.keyInput.setMaxLength(self.maxKeyLen)
        self.keyInput.setAlignment(Qt.AlignCenter)
        self.keyInput.textEdited.connect(self.showKeyLen)

        self.genKeyButton = QPushButton('&Gen Key')  #\U0001F511
        self.genKeyButton.setToolTip('Generate a random key')
        self.genKeyButton.setMinimumSize(60, 20)
        self.genKeyButton.setMaximumSize(60, 20)
        self.genKeyButton.setSizePolicy(Fixed)
        self.genKeyButton.clicked.connect(self.genKey)

        self.keySizeSB = QSpinBox()
        self.keySizeSB.setToolTip('Length of key to generate')
        self.keySizeSB.setRange(32, 1024)
        self.keySizeSB.setMinimumSize(40, 20)
        self.keySizeSB.setMaximumSize(40, 20)
        self.keySizeSB.setSizePolicy(Fixed)
        self.keySizeSB.setAlignment(Qt.AlignCenter)
        self.keySizeSB.setButtonSymbols(QSpinBox.NoButtons)
        self.keySizeSB.setWrapping(True)

        self.hl02.insertWidget(0, self.cancelButton)
        self.hl02.insertWidget(1, self.keyInput)
        self.hl02.insertWidget(2, self.genKeyButton)
        self.hl02.insertWidget(3, self.keySizeSB)

        # right column - fourth item (4; horizontal layout 3)
        self.hl03 = QHBoxLayout()
        self.hl03.setSpacing(5)

        self.decryptButton = QPushButton('&Decrypt')  #\U0001F513
        self.decryptButton.setToolTip('Decrypt selected file')
        self.decryptButton.setMinimumSize(60, 20)
        self.decryptButton.setMaximumSize(60, 20)
        self.decryptButton.setSizePolicy(Fixed)
        self.decryptButton.clicked.connect(self.decrypt)

        self.decryptPbar = QProgressBar()
        self.decryptPbar.setMinimumSize(225, 20)
        self.decryptPbar.setMaximumSize(16777215, 20)
        self.decryptPbar.setSizePolicy(MinimumExpanding)
        self.decryptPbar.setTextVisible(False)
        self.decryptPbar.setInvertedAppearance(True)

        palette = self.decryptPbar.palette()  # color of progress bar
        color = QColor(0, 170, 255)
        palette.setColor(QPalette.Highlight, color)
        self.decryptPbar.setPalette(palette)

        self.hl03.insertWidget(0, self.decryptButton)
        self.hl03.insertWidget(1, self.decryptPbar)

        # right column - fifth item (7; horizontal layout 4)
        self.hl04 = QHBoxLayout()
        self.hl04.setSpacing(5)

        self.showKeyCB = QCheckBox('&Show Key')
        self.showKeyCB.setToolTip('Show/Hide key value')
        self.showKeyCB.setMinimumSize(75, 20)
        self.showKeyCB.setMaximumSize(75, 20)
        self.showKeyCB.setSizePolicy(Fixed)
        self.showKeyCB.clicked.connect(self.showKey)
        self.showKeyCB.setChecked(True)

        self.hashPbar = QProgressBar()
        self.hashPbar.setMinimumSize(150, 20)
        self.hashPbar.setMaximumSize(16777215, 20)
        self.hashPbar.setSizePolicy(MinimumExpanding)
        self.hashPbar.setTextVisible(False)

        palette = self.hashPbar.palette()  # color of progress bar
        color = QColor(31, 120, 73)
        palette.setColor(QPalette.Highlight, color)
        self.hashPbar.setPalette(palette)

        self.hashButton = QPushButton('&Hash')
        self.hashButton.setToolTip('Determine file hash')
        self.hashButton.setMinimumSize(60, 20)
        self.hashButton.setMaximumSize(60, 20)
        self.hashButton.setSizePolicy(Fixed)

        menu = QMenu(self.hashButton)
        ico = self.style().standardIcon(QStyle.SP_DialogYesButton)
        for alg in sorted(
                filter(lambda x: 'shake' not in x,
                       hashlib.algorithms_guaranteed),
                key=lambda n:
            (len(n), sorted(hashlib.algorithms_guaranteed).index(n))):
            menu.addAction(
                ico, alg
            )  # drop shake algs as their .hexdigest requires an argument - the rest don't
        menu.addAction(ico, 'Party')
        for i in menu.actions():
            i.setIconVisibleInMenu(False)
        self.hashButton.setMenu(menu)
        menu.triggered.connect(self.genHash)

        self.hl04.insertWidget(0, self.showKeyCB)
        self.hl04.insertWidget(1, self.hashPbar)
        self.hl04.insertWidget(2, self.hashButton)

        # right column - sixth item (8; horizontal layout 5)
        self.hl05 = QHBoxLayout()
        self.hl05.setSpacing(5)

        self.copyButton = QPushButton('&Copy')  #\U0001F4CB
        self.copyButton.setToolTip('Copy key or hash to clipboard')
        self.copyButton.setMinimumSize(60, 20)
        self.copyButton.setMaximumSize(60, 20)
        self.copyButton.setSizePolicy(Fixed)

        menu2 = QMenu(self.copyButton)
        menu2.addAction('Copy Key')
        menu2.addAction('Copy Hash')
        self.copyButton.setMenu(menu2)
        menu2.triggered.connect(self.copyKeyHash)

        self.hashLabel = QLabel()
        self.hashLabel.setMinimumSize(225, 20)
        self.hashLabel.setMaximumSize(16777215, 20)
        self.hashLabel.setSizePolicy(MinimumExpanding)
        self.hashLabel.setTextFormat(Qt.PlainText)
        self.hashLabel.setAlignment(Qt.AlignCenter)
        self.hashLabel.setTextInteractionFlags(Qt.TextSelectableByMouse)

        self.hl05.insertWidget(0, self.copyButton)
        self.hl05.insertWidget(1, self.hashLabel)

        # finalize right column
        self.vl02.insertWidget(0, self.messageLabel)
        self.vl02.insertSpacerItem(1, QSpacerItem(0, 0))
        self.vl02.insertLayout(2, self.hl01)
        self.vl02.insertLayout(3, self.hl02)
        self.vl02.insertLayout(4, self.hl03)
        self.vl02.insertSpacerItem(5, QSpacerItem(0, 0))
        self.vl02.insertWidget(6, QFrame())
        self.vl02.insertLayout(7, self.hl04)
        self.vl02.insertLayout(8, self.hl05)
        self.rightColumn.setLayout(self.vl02)

        # finalize main window
        self.splitter.insertWidget(0, self.leftColumn)
        self.splitter.insertWidget(1, self.rightColumn)

        layout = QHBoxLayout(self)
        layout.addWidget(self.splitter)
        self.setLayout(layout)

        self.setWindowTitle('Simple File Encryptor/Decryptor')
        self.resize(self.sizeHint())
Exemple #10
0
class ProcessListWidget(QScrollArea):
    action_requested = Signal(str, str)
    process_state_changed = Signal(str, str)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.process_data = set()
        self.process_widget_map = {}  # type: Dict[str, ProcessWidget]

        self.main_widget = QWidget()
        self.process_layout = QVBoxLayout()
        self.process_layout.setContentsMargins(2, 2, 2, 2)
        self.main_widget.setLayout(self.process_layout)

        self.lbl_default = QLabel(text="No processes found")
        self.process_layout.addWidget(self.lbl_default)
        self.process_layout.addStretch()

        # Configure ScrollArea
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setWidgetResizable(True)
        self.setWidget(self.main_widget)
        self.setSizePolicy(QSizePolicy.MinimumExpanding,
                           QSizePolicy.MinimumExpanding)

    def on_action_completed(self, response: ActionResponse):
        logger.info("Action completed: %s", response)
        self.process_widget_map[response.uid].on_action_completed(
            response.action)

    def update_single_process_state(self, event: StateChangedEvent):
        self.process_widget_map[event.uid].update_state(
            event.state, event.exit_code)

    def update_process_data(self, updated_process_data: Set[ProcessData]):
        potentially_updated = set()
        for process_data in updated_process_data:
            if process_data in self.process_data:
                potentially_updated.add(process_data)

        new_processes = updated_process_data - self.process_data
        unknown_processes = self.process_data - updated_process_data
        print(potentially_updated, new_processes, unknown_processes)
        # TODO(mark): update process data sets with new data
        for known_process in potentially_updated:
            widget = self.process_widget_map[known_process.uid]
            widget.on_update_process_data(known_process)

        for new_process in new_processes:
            widget = ProcessWidget(new_process)
            self.process_widget_map[new_process.uid] = widget
            widget.actionRequested.connect(self.action_requested)
            widget.process_state_changed.connect(self.process_state_changed)
            self.process_layout.insertWidget(0, widget)

        # TODO(mark): unknown processes
        for process in unknown_processes:
            widget = self.process_widget_map[process.uid]
            logger.info("Removing widget: %s", process.uid)
            self.process_layout.removeWidget(widget)
            del self.process_widget_map[process.uid]
            widget.deleteLater()

        self.process_data = new_processes | potentially_updated
        self.lbl_default.setVisible(len(self.process_data) == 0)

        return new_processes, unknown_processes
Exemple #11
0
class ViewWidget(QWidget):
    exercise_name_label = None
    exercise_name_line = None

    scroll_area = None
    base_widget = None
    exercises_widget = None

    return_button = None

    add_button = None

    def __init__(self):
        QWidget.__init__(self)

        self.file = ""
        self.setup_widget()

    def setup_widget(self):
        self.exercise_name_label = QLabel("Exercise name:", self)
        self.exercise_name_label.move(5, 5)
        self.exercise_name_label.resize(125, 25)

        self.add_button = QPushButton("Add", self)
        self.add_button.resize(75, 25)
        self.add_button.clicked.connect(self.add_line)

        self.exercise_name_line = QLineEdit(self)
        self.exercise_name_line.move(135, 5)
        self.exercise_name_line.resize(125, 25)

        self.scroll_area = QScrollArea(self)
        self.base_widget = QWidget(self)
        self.scroll_area.setWidget(self.base_widget)

        self.exercises_widget = QVBoxLayout()
        self.exercises_widget.setAlignment(Qt.AlignTop)

        self.base_widget.setLayout(self.exercises_widget)

        self.return_button = QPushButton("Return wo save", self)

    def resizeEvent(self, event):
        self.scroll_area.move(5, 35)
        self.scroll_area.resize(self.width() - 165, self.height() - 40)
        self.add_button.move(self.width() - 160 - 75, 5)
        self.return_button.move(self.width() - 155, 5)
        self.return_button.resize(150, 40)

        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

    def clear_widget(self):
        while self.exercises_widget.count() > 0:
            self.exercises_widget.takeAt(0)

    def open_exercise_file(self, file: str):
        self.file = file

        with open(self.file, "r") as json_file:
            json_data = json.load(json_file)

            name = json_data['name']

            for data in json_data['exercise']:
                movement = data['name']
                description = data['description']
                time = data['time']

                widget = PanelWidget()
                widget.set_data(movement, description, time)
                widget.remove_signal.connect(self.remove_panel_item)
                widget.move_down_signal.connect(self.move_widget_down)
                widget.move_up_signal.connect(self.move_widget_up)

                self.exercises_widget.addWidget(widget)

            json_file.close()

        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

        self.exercise_name_line.setText(name)

    @Slot()
    def add_line(self):
        widget = PanelWidget()
        self.exercises_widget.addWidget(widget)
        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)

    @Slot(QWidget)
    def move_widget_down(self, widget: QWidget):
        ind = self.exercises_widget.indexOf(widget)
        self.exercises_widget.removeWidget(widget)
        self.exercises_widget.insertWidget((ind + 1), widget)

    @Slot(QWidget)
    def move_widget_up(self, widget: QWidget):
        ind = self.exercises_widget.indexOf(widget)
        self.exercises_widget.removeWidget(widget)
        self.exercises_widget.insertWidget((ind - 1), widget)

    @Slot(QWidget)
    def remove_panel_item(self, widget: QWidget):
        self.exercises_widget.removeWidget(widget)
        self.base_widget.resize(self.scroll_area.width() - 25,
                                self.exercises_widget.count() * 25)
Exemple #12
0
class Central(QFrame):
    '''Initializes, styles, and connects the various classes'''

    def __init__(self):
        super().__init__()
        # Objects
        self.overallLayout = QVBoxLayout(self)
        self.contentLayout = QHBoxLayout()
        self.dropShadow = QGraphicsDropShadowEffect(self)
        self.boxManager = BoxManager.BoxManager()
        self.topBar = TopBar.TopBar()
        self.selectorArea = QFrame()
        self.selectorLayout = QVBoxLayout(self.selectorArea)
        self.folderArea = QFrame()
        self.folderLayout = QHBoxLayout(self.folderArea)
        self.folderList = FolderList.FolderList()
        self.folderBar = ScrollBar.ScrollBar(self.folderList)
        self.canvas = Canvas.Canvas(self.boxManager)
        self.imageArea = QFrame()
        self.imageList = ImageList.ImageList()
        self.imageLayout = QHBoxLayout(self.imageArea)
        self.imageBar = ScrollBar.ScrollBar(self.imageList)

        # Styling
        self.setStyleSheet('Central { background: transparent; }')
        self.overallLayout.setMargin(20)
        self.overallLayout.setSpacing(0)
        self.dropShadow.setOffset(QPointF(0,4))
        self.dropShadow.setColor(QColor(0,0,0,100))
        self.dropShadow.setBlurRadius(10)
        self.setGraphicsEffect(self.dropShadow)
        self.contentLayout.setAlignment(Qt.AlignCenter)
        self.contentLayout.setMargin(0)        
        self.contentLayout.setSpacing(0)
        self.selectorLayout.setMargin(0)
        self.selectorLayout.setSpacing(0)
        self.folderLayout.setMargin(0)
        self.folderLayout.setSpacing(0)
        self.imageLayout.setMargin(0)
        self.imageLayout.setSpacing(0)
        self.folderList.setVerticalScrollBar(self.folderBar)
        self.imageList.setVerticalScrollBar(self.imageBar)
        self.selectorArea.setMaximumWidth(400)
        self.selectorArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        # Layout
        self.folderLayout.addWidget(self.folderList)
        self.folderLayout.addSpacerItem(QSpacerItem(-7, 0))
        self.folderLayout.addWidget(self.folderBar)
        self.imageLayout.addWidget(self.imageList)
        self.imageLayout.addSpacerItem(QSpacerItem(-7, 0))
        self.imageLayout.addWidget(self.imageBar)
        self.selectorLayout.addWidget(self.folderArea, 15)
        self.selectorLayout.addWidget(self.imageArea, 85)
        self.contentLayout.addWidget(self.selectorArea, 30)
        self.contentLayout.addWidget(self.canvas, 70)
        self.overallLayout.addLayout(self.contentLayout)
        self.overallLayout.insertWidget(0, self.topBar)

        # Connections
        self.folderList.selectedFolderChanged.connect(self.handleSelectedFolderChanged)
        self.imageList.selectedImageChanged.connect(self.handleSelectedImageChanged)

    def handleSelectedFolderChanged(self, folder):
        self.imageList.populate(folder)
        self.canvas.changeImage(None)
        self.canvas.setMessage('Switching Folders - {}'.format(folder.data(role=Qt.DisplayRole)))
        self.topBar.setSelectedFolder(str(folder.data(role=Qt.UserRole+1)))
        self.topBar.setSelectedImage('')

    def handleSelectedImageChanged(self, image):
        self.canvas.changeImage(image)
        self.canvas.setMessage('Switching Images - {}'.format(image.data(role=Qt.DisplayRole)))
        self.topBar.setSelectedImage(str(image.data(role=Qt.DisplayRole)))
Exemple #13
0
class SettingsUI(MayaQWidgetDockableMixin, QWidget):

    save_prefs = Signal(dict)

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

        #  log_lvl = sys._global_spore_dispatcher.spore_globals['LOG_LEVEL']
        #  self.logger = logging_util.SporeLogger(__name__, log_lvl)
        self.build_ui()
        self.pref_tracking_dir = {}

    def build_ui(self):
        self.setWindowTitle('Spore Preferences')
        self.setGeometry(250, 250, 400, 150)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)
        self.layout.addStretch()

        self.save_btn = QPushButton('Save')
        self.layout.addWidget(self.save_btn)

        self.save_btn.clicked.connect(self.about_to_save)

    def add_pref_wdg(self, name, value):

        if isinstance(value, bool):
            wdg = BoolWidget(name, value)
            self.layout.insertWidget(0, wdg)
            self.pref_tracking_dir[wdg.name_lbl] = wdg.bool_cbx
        elif isinstance(value, str) or isinstance(value, unicode):
            wdg = StringWidget(name, value)
            self.layout.insertWidget(0, wdg)
            self.pref_tracking_dir[wdg.name_lbl] = wdg.line_edt
        elif isinstance(value, int):
            wdg = IntegerWidget(name, value)
            self.layout.insertWidget(0, wdg)
            self.pref_tracking_dir[wdg.name_lbl] = wdg.int_spn
        elif isinstance(value, float):
            wdg = FloatWidget(name, value)
            self.layout.insertWidget(0, wdg)
            self.pref_tracking_dir[wdg.name_lbl] = wdg.float_spn
        else:
            print 'no assignment for', name, value, type(value)
            pass

    def about_to_save(self):
        """ triggered by the save button.
        emit the save_prefs signal and send the resulting settings """

        #  self.logger.debug('About to emit save settings.')
        prefs = {}
        for attr_lbl, val_wdg in self.pref_tracking_dir.iteritems():
            attr = attr_lbl.text()

            val = None
            if isinstance(val_wdg, QCheckBox):
                val = val_wdg.isChecked()
            elif isinstance(val_wdg, QLineEdit):
                val = val_wdg.text()
            elif isinstance(val_wdg, QSpinBox)\
            or isinstance(val_wdg, QDoubleSpinBox):
                val = val_wdg.value()
            else:
                #  self.logger.error('Unknown widget type: {}'.format(val_wdg))
                raise RuntimeError('Impossible')

            prefs[attr] = val

        self.save_prefs.emit(prefs)
        self.close()
class GroupOgen(QGroupBox):
    def __init__(self, parent):
        """Groupbox for Ogen module fields filling form."""
        super().__init__()
        self.setTitle('Ogen')
        self.group_type = 'Ogen'
        self.groups_list = []

        self.vbox1 = QVBoxLayout()
        self.vbox1.addStretch()

        self.grid = QGridLayout()
        self.grid.setColumnStretch(5, 1)
        self.grid.addLayout(self.vbox1, 2, 0, 1, 6)
        self.setLayout(self.grid)

    def add_group(self, group, metadata=None):
        """Adds group form."""
        if metadata is not None:
            group.write_fields(metadata=metadata)
        group.form_name.textChanged.connect(self.refresh_children)
        self.groups_list.append(group)
        nWidgetsVbox = self.vbox1.count()
        self.vbox1.insertWidget(nWidgetsVbox - 1,
                                group)  # insert before the stretch
        self.refresh_children(metadata=metadata)

    def is_referenced(self, grp_unique_name):
        """Tests if a group is being referenced any other groups. Returns boolean."""
        nWidgetsVbox = self.vbox1.count()
        for i in range(nWidgetsVbox):
            if self.vbox1.itemAt(i).widget() is not None:
                other_grp = self.vbox1.itemAt(i).widget()
                # check if this subgroup has any ComboBox referencing grp_unique_name
                for ch in other_grp.children():
                    if isinstance(ch, (CustomComboBox, QComboBox)):
                        if ch.currentText() == grp_unique_name:
                            return True
        return False

    def refresh_children(self, metadata=None):
        """Refreshes references with existing objects in child groups."""
        for child in self.groups_list:
            child.refresh_objects_references(metadata=metadata)

    def read_fields(self):
        """Reads fields and returns them structured in a dictionary."""
        error = None
        data = {}
        # group_type counts, if there are multiple groups of same type, they are saved in a list
        grp_types = [grp.group_type for grp in self.groups_list]
        grp_type_count = {
            value: len(list(freq))
            for value, freq in groupby(sorted(grp_types))
        }
        # initiate lists as values for groups keys with count > 1
        for k, v in grp_type_count.items():
            if v > 1 or k == 'Device' or k == 'OptogeneticStimulusSite' or k == 'OptogeneticSeries':
                data[k] = []
        # iterate over existing groups and copy their metadata
        for grp in self.groups_list:
            if grp_type_count[grp.group_type] > 1 or grp.group_type == 'Device' \
               or grp.group_type == 'OptogeneticStimulusSite' \
               or grp.group_type == 'OptogeneticSeries':
                data[grp.group_type].append(grp.read_fields())
            else:
                data[grp.group_type] = grp.read_fields()
        return data, error
Exemple #15
0
class ComponentTreeViewTab(QWidget):
    def __init__(self, parent=None):
        super().__init__()
        self.setLayout(QVBoxLayout())
        self.setParent(parent)
        self.componentsTabLayout = QVBoxLayout()
        self.component_tree_view = QTreeView()
        self.componentsTabLayout.addWidget(self.component_tree_view)
        self.layout().addLayout(self.componentsTabLayout)

        self.component_tree_view.setDragEnabled(True)
        self.component_tree_view.setAcceptDrops(True)
        self.component_tree_view.setDropIndicatorShown(True)
        self.component_tree_view.header().hide()
        self.component_tree_view.updateEditorGeometries()
        self.component_tree_view.updateGeometries()
        self.component_tree_view.updateGeometry()
        self.component_tree_view.clicked.connect(self._set_button_state)
        self.component_tree_view.setSelectionMode(QAbstractItemView.SingleSelection)

        self.component_tool_bar = QToolBar("Actions", self)
        self.new_component_action = create_and_add_toolbar_action(
            "new_component.png",
            "New Component",
            self.parent().show_add_component_window,
            self.component_tool_bar,
            self,
            True,
        )
        self.new_translation_action = create_and_add_toolbar_action(
            "new_translation.png",
            "New Translation",
            lambda: self._add_transformation(TransformationType.TRANSLATION),
            self.component_tool_bar,
            self,
        )
        self.new_rotation_action = create_and_add_toolbar_action(
            "new_rotation.png",
            "New Rotation",
            lambda: self._add_transformation(TransformationType.ROTATION),
            self.component_tool_bar,
            self,
        )
        self.create_link_action = create_and_add_toolbar_action(
            "create_link.png",
            "Create Link",
            self.on_create_link,
            self.component_tool_bar,
            self,
        )
        self.duplicate_action = create_and_add_toolbar_action(
            "duplicate.png",
            "Duplicate",
            self.on_duplicate_node,
            self.component_tool_bar,
            self,
        )
        self.edit_component_action = create_and_add_toolbar_action(
            "edit_component.png",
            "Edit Component",
            self.parent().show_edit_component_dialog,
            self.component_tool_bar,
            self,
        )
        self.delete_action = create_and_add_toolbar_action(
            "delete.png", "Delete", self.on_delete_item, self.component_tool_bar, self
        )
        self.zoom_action = create_and_add_toolbar_action(
            "zoom.svg",
            "Zoom To Component",
            self.on_zoom_item,
            self.component_tool_bar,
            self,
        )
        self.component_tool_bar.insertSeparator(self.zoom_action)
        self.componentsTabLayout.insertWidget(0, self.component_tool_bar)

    def set_up_model(self, instrument):
        self.component_model = ComponentTreeModel(instrument)
        self.component_delegate = ComponentEditorDelegate(
            self.component_tree_view, instrument
        )
        self.component_tree_view.setItemDelegate(self.component_delegate)
        self.component_tree_view.setModel(self.component_model)

    def _set_button_state(self):
        set_button_states(
            self.component_tree_view,
            self.delete_action,
            self.duplicate_action,
            self.new_rotation_action,
            self.new_translation_action,
            self.create_link_action,
            self.zoom_action,
            self.edit_component_action,
        )

    def on_create_link(self):
        selected = self.component_tree_view.selectedIndexes()
        if len(selected) > 0:
            self.component_model.add_link(selected[0])
            self._expand_transformation_list(selected[0])
            self._set_button_state()

    def on_duplicate_node(self):
        selected = self.component_tree_view.selectedIndexes()
        if len(selected) > 0:
            self.component_model.duplicate_node(selected[0])
            self._expand_transformation_list(selected[0])

    def _expand_transformation_list(self, node: QModelIndex):
        expand_transformation_list(node, self.component_tree_view, self.component_model)

    def _add_transformation(self, transformation_type: TransformationType):
        add_transformation(
            transformation_type, self.component_tree_view, self.component_model
        )

    def on_delete_item(self):
        selected = self.component_tree_view.selectedIndexes()
        for item in selected:
            self.component_model.remove_node(item)
        self._set_button_state()

    def on_zoom_item(self):
        selected = self.component_tree_view.selectedIndexes()[0]
        component = selected.internalPointer()
        self.sceneWidget.zoom_to_component(
            self.sceneWidget.get_entity(component.name), self.sceneWidget.view.camera()
        )
Exemple #16
0
class AbsOperationEditor(QWidget):
    """ Base class for operation editors. Provide editors made of a custom widget and two buttons,
    one to accept and one to close and reject changes. Pressing of one of these two buttons
    emits one of two signals:

        - accept
        - reject
    """
    # Signal to emit when editing is finished (must be class object)
    accept = Signal()
    reject = Signal()

    # ----------------------------------------------------------------------------
    # ---------------------- FINAL METHODS (PLS NO OVERRIDE) ---------------------
    # ----------------------------------------------------------------------------

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

        # Standard options
        self.errorHandlers: Dict[str, Callable] = dict()
        self.acceptedTypes: List[Type] = list()
        self.inputShapes: List[Optional[data.Shape]] = list()
        self.workbench: 'WorkbenchModel' = None

        # Set up buttons
        self._butOk = QPushButton('Ok')
        butCancel = QPushButton('Cancel')
        self.butLayout = QHBoxLayout()
        self.butLayout.addWidget(butCancel, alignment=Qt.AlignLeft)
        self.butLayout.addWidget(self._butOk, alignment=Qt.AlignRight)

        self.__helpVerticalLayout = QVBoxLayout()
        self.__helpLayout = QHBoxLayout()
        self.__descLabel = QLabel()
        self.__helpLayout.addWidget(self.__descLabel, 7)
        self.__descLabel.setSizePolicy(QSizePolicy.MinimumExpanding,
                                       QSizePolicy.Maximum)
        self.__helpVerticalLayout.addLayout(self.__helpLayout)
        ll = QLabel('<hr>')
        ll.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.__helpVerticalLayout.addWidget(ll)

        self.errorLabel = QLabel(self)
        self.errorLabel.setWordWrap(True)
        self.errorLabel.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)

        self._layout = QVBoxLayout()
        self._layout.addLayout(self.__helpVerticalLayout, 1)
        self._layout.addWidget(self.errorLabel, 1)
        ll = QLabel('<hr>')
        ll.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self._layout.addWidget(ll, 1)
        self._layout.addLayout(self.butLayout, 1)
        self.setLayout(self._layout)
        self.setFocusPolicy(Qt.StrongFocus)
        self.errorLabel.hide()
        self.setMinimumWidth(500)

        self._butOk.pressed.connect(self.onAcceptSlot)
        butCancel.pressed.connect(self.reject)  # emit reject
        self.__sh: Optional[QSize] = None  # Qt sizeHint property
        self._infoBalloon = None

    def setDescription(self, short: str, long: str) -> None:
        self.__descLabel.setText(short)
        self.__descLabel.setWordWrap(True)
        if long:
            whatsThisButton = QPushButton('More')
            whatsThisButton.setSizePolicy(QSizePolicy.Maximum,
                                          QSizePolicy.Maximum)
            self.__helpLayout.addWidget(whatsThisButton, 1)
            whatsThisButton.clicked.connect(lambda: self.showInfoBalloon(long))
            # self.setWhatsThis(long)
            # whatsThisButton.clicked.connect(
            #     lambda: QWhatsThis.showText(QCursor.pos(), long, self))

    @Slot(str)
    def showInfoBalloon(self, long: str) -> None:
        if not self._infoBalloon:
            self._infoBalloon = InfoBalloon(self)
        self._infoBalloon.setText(long)
        self._infoBalloon.show()
        self._infoBalloon.move(QCursor.pos())

    def closeEvent(self, event: QCloseEvent) -> None:
        """ Reject changes and close editor if the close button is pressed """
        self.reject.emit()

    def setUpEditor(self):
        """ Calls editorBody and add the returned widget """
        w = self.editorBody()
        w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)
        self._layout.insertWidget(1, w)

    def handleErrors(self, errors: List[Tuple[str, str]]) -> None:
        """ Provide a list of readable errors to be shown in the widget

        :param errors: list of (errorName, errorMessage). If 'errorName' is available in
            the widget errorHandlers field the corresponding callback will be fired with the 'errorMessage'
            This is useful to show custom error messages in specific parts of the editor widget

        """
        self.errorLabel.hide()
        text = ''
        for (field, message) in errors:
            handler = self.errorHandlers.get(field, None)
            if handler:
                handler(message)
            else:
                text += '<br>' + message
        text = text.strip('<br>')
        if text:
            # Default message on bottom
            self.errorLabel.setText(text)
            self.errorLabel.setStyleSheet('color: red;')
            self.errorLabel.show()

    def disableOkButton(self) -> None:
        """ Makes the accept button uncheckable.
            Useful to prevent user from saving invalid changes """
        self._butOk.setDisabled(True)

    def enableOkButton(self) -> None:
        """ Enable the accept button """
        self._butOk.setEnabled(True)

    @Slot()
    def onAcceptSlot(self) -> None:
        self.onAccept()
        self.accept.emit()

    def setSizeHint(self, w: int, h: int) -> None:
        """ Set the editor sizeHint property.
         This function is provided for convenience: it is equivalent to reimplementing sizeHint()
         directly in a subclass.
        """
        self.__sh = QSize(w, h)

    def sizeHint(self) -> QSize:
        if self.__sh:
            return self.__sh
        return super().sizeHint()

    def keyPressEvent(self, event: QKeyEvent) -> None:
        if event.key() == Qt.Key_Escape:
            self.reject.emit()
        else:
            super().keyPressEvent(event)

    # ----------------------------------------------------------------------------
    # ------------------------------ VIRTUAL METHODS -----------------------------
    # ----------------------------------------------------------------------------

    @abc.abstractmethod
    def editorBody(self) -> QWidget:
        """
        Hook method to add widget components. This may include Qt components of every kind, as long as
        they can belong to a QWidget. This method may add fields to the instance but must return the
        widget that should be shown in the editor. This method is called after the constructor
        """
        pass

    @abc.abstractmethod
    def getOptions(self) -> Iterable:
        """
        Return the arguments read by the editor.
        Must be an iterable and parameters are passed in the same order. If
        no options are set return a list with None values

        :return: the options currently set by the user in the editor
        """
        pass

    def setOptions(self, *args, **kwargs) -> None:
        """
        Set the data to be visualized in the editor.
        Useful to show an existing configuration. Does nothing by default

        :param args: any positional argument.

        """
        pass

    def onAccept(self) -> None:
        """
        Method called when the user accepts current options (e.g. clicks the Ok button),
        immediately before emitting the 'accept' signal. Useful to do additional actions over
        options before setting them in the operation. Does nothing by default
        """
        pass
class InstallingMissingDependencies(QDialog):
    """
    At start, dEaduction checks if missing dependencies are missing. If
    some indeed are, usr is asked is they want to install them
    (ReallyWantQuit). If they do, download begins and this dialog is
    shown showing a logger of the installation.

    When the download is completed, the 'Start dEAduction' button is
    activated (method installation_completed) and usr may click on it.
    If they do, the signal plz_start_deaduction is emitted. Furthermore,
    the usr may want to quit (by clicking the 'Quit' button). If they do
    so during the installation, they are asked for confirmation (see
    __quit method) and if they confirm, the signal plz_quit is emitted.

    The logger is displayed with the class TextEditLogger.
    """

    plz_start_deaduction = Signal()
    plz_quit = Signal()

    def __init__(
            self,
            log_format: str = '%(asctime)s - %(levelname)s - %(message)s'):
        """
        Init self with a logger formater (so specify the layout of the
        log entries, see logging module documentation), e.g.
        '%(asctime)s - %(levelname)s - %(message)s'.

        :param log_format: Logger formatter for the log entries.
        """

        super().__init__()
        self.setModal(True)

        self.setWindowTitle(f"{_('Installing missing dependencies')}" \
                             " — d∃∀duction")
        self.__text_edit_logger = TextEditLogger()
        self.__confirm_quit = True

        # Buttons
        self.__quit_btn = QPushButton(_('Quit'))
        self.__start_dead_btn = QPushButton(_('Start d∃∀duction'))
        self.__quit_btn.setAutoDefault(False)
        self.__start_dead_btn.setEnabled(False)
        self.__quit_btn.clicked.connect(self.__quit)
        self.__start_dead_btn.clicked.connect(self.plz_start_deaduction)

        # Layouts
        self.__main_layout = QVBoxLayout()
        btns_layout = QHBoxLayout()
        btns_layout.addStretch()
        btns_layout.addWidget(self.__quit_btn)
        btns_layout.addWidget(self.__start_dead_btn)
        self.__main_layout.addWidget(self.__text_edit_logger)
        self.__main_layout.addLayout(btns_layout)
        self.setLayout(self.__main_layout)

        # Logging facilities, avoid some segfault and thread-related nastyness
        self.__text_edit_logger_handler = TextEditLoggerHandler(
            self.__text_edit_logger, log_format)
        self.__log_queue = Queue(-1)
        self.__queue_handler = QueueHandler(self.__log_queue)
        self.__queue_listener = QueueListener(self.__log_queue,
                                              self.__text_edit_logger_handler)

    def log_attach(self, log_obj: logging.Logger):
        log_obj.addHandler(self.__queue_handler)

    def log_dettach(self, log_obj: logging.Logger):
        log_obj.removeHandler(self.__queue_handler)

    def log_start(self):
        self.__queue_listener.start()

    def log_stop(self):
        self.__queue_listener.stop()

    @Slot()
    def installation_completed(self):
        """
        This function is to be called when dependencies are all
        downloaded and the installation is completed:
        - it indicates all went good;
        - it allows usr to click on the 'Start dEAduction button';
        - it sets self.__confirm_quit to False, which implies that if
          usr wants to quit instead of starting dEAduction, confirmation
          will *not* be asked.

        It is recommanded (to comply with Qt's style) to connect this
        method to a slot and not call it directly.
        """

        self.__confirm_quit = False
        self.__text_edit_logger.setStyleSheet('background: SpringGreen;')
        self.__main_layout.insertWidget(
            1, QLabel(_('Missing dependencies installed.')))
        self.__start_dead_btn.setEnabled(True)
        self.__start_dead_btn.setDefault(True)
        self.__start_dead_btn.clicked.connect(self.plz_start_deaduction)

    @Slot()
    def __quit(self):
        """
        This slot is called when usr clicks on the 'Quit' button. If
        downloads and installation are not completed, confirmation is
        asked by executing ReallyWantQuit dialog. In the end, if usr
        really wants to quit, the signal plz_quit is emitted (here is
        not the place to quit).
        """

        if self.__confirm_quit:
            rwtq = ReallyWantQuit(_('All downloaded data will be lost.'))
            rwtq.exec_()
            if rwtq.yes:
                self.plz_quit.emit()
        else:
            self.plz_quit.emit()
Exemple #18
0
class ConfigWindow(QWidget):
    def __init__(self, current_profile):
        """
        The ConfigWindow is a widget dedicated to reading and editing the OCI config file and provides functionality to create, edit, and switch profiles on the fly, updating
        the view of the main window.

        :param current_profile: The profile the ConfigWindow should be initialized with
        :type current_profile: string
        """
        super().__init__()

        #
        self.main_window = None
        self.setWindowTitle("Profile Settings")
        self.setMinimumSize(600, 200)

        #Looks for the config file in '~/.oci/config' and reads it into config
        self.DEFAULT_LOCATION = os.path.expanduser(
            os.path.join('~', '.oci', 'config'))
        self.config = configparser.ConfigParser(interpolation=None)
        self.config.read(self.DEFAULT_LOCATION)
        self.current_profile = current_profile

        #Set up necessary dropdown and LineEdit widgets
        self.dropdown = self.get_profiles_dropdown()
        self.tenancy = QLineEdit()
        self.tenancy.setPlaceholderText("Tenancy OCID")
        self.region = QLineEdit()
        self.region.setPlaceholderText("Region")
        self.user = QLineEdit()
        self.user.setPlaceholderText("User OCID")
        self.fingerprint = QLineEdit()
        self.fingerprint.setPlaceholderText("Fingerprint")
        self.key_file = QLineEdit()
        self.key_file.setPlaceholderText("Key File Path")
        self.passphrase = QLineEdit()
        self.passphrase.setEchoMode(QLineEdit.Password)
        self.passphrase.setPlaceholderText("Passphrase")
        self.save_button = QPushButton('Save')
        self.save_button.clicked.connect(self.save_signal)

        #Set the profile to the current_profile passed in upon init
        self.change_profile(current_profile)
        self.dropdown.setCurrentText(current_profile)

        #Add all widgets to a vertical layout
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.dropdown)
        self.layout.addWidget(self.tenancy)
        self.layout.addWidget(self.region)
        self.layout.addWidget(self.user)
        self.layout.addWidget(self.key_file)
        self.layout.addWidget(self.fingerprint)
        self.layout.addWidget(self.passphrase)
        self.layout.addWidget(self.save_button)

        self.setLayout(self.layout)

    def get_profiles_dropdown(self):
        """
        :return:
            A dropdown menu widget that lists all profiles including the default profile from the OCI config file
            When index changes, it will call the change_profile signal function
        :rtype: :class: 'Qt.QtWidgets.QComboBox'
        """
        dropdown = QComboBox()
        dropdown.addItems(['DEFAULT'] + self.config.sections())
        dropdown.addItem("Add New Profile...")
        dropdown.currentIndexChanged.connect(self.change_profile_signal)
        return dropdown

    def change_profile_signal(self, item):
        """
        Slot to change profile. If the item index is at 0, then it is the default profile.
        If it is the last index, then that means create a new profile

        :param item: The index of the item from the dropdown widget
        :type item: int
        """
        if item > len(self.config.sections()):
            self.create_new_profile()
        elif item == 0:
            self.change_profile('DEFAULT')
        else:
            self.change_profile(self.config.sections()[item - 1])

    def change_profile(self, profile_name):
        """
        Changes the profile that the ConfigWindow is set for and also changes it for the MainWindow

        :param profile_name: the name of the profile to switch to
        :type profile_name: string

        TODO: Adhere to signal/slot convention
        """
        self.current_profile = profile_name
        profile = self.config[profile_name]

        for line, key in zip([self.tenancy, self.region, self.user, self.fingerprint, self.key_file, self.passphrase],\
        ['tenancy', 'region', 'user', 'fingerprint', 'key_file', 'pass_phrase']):
            if key in profile:
                line.setText(profile[key])
            else:
                line.setText("")

        if self.main_window:
            self.main_window.change_profile(self.current_profile)

    def create_new_profile(self):
        """
        Layout to create a new profile. Removes the dropdown widget and changes the buttons
        """
        self.layout.removeItem(self.layout.itemAt(0))
        self.dropdown.setParent(None)

        self.new_profile_name = QLineEdit()
        self.new_profile_name.setPlaceholderText("Profile Name")
        self.layout.insertWidget(0, self.new_profile_name)

        self.tenancy.setText("")
        self.region.setText("")
        self.user.setText("")
        self.fingerprint.setText("")
        self.key_file.setText("")
        self.passphrase.setText("")
        self.create_button = QPushButton('Create')
        self.create_button.clicked.connect(self.create_signal)
        self.cancel_button = QPushButton('Cancel')
        self.cancel_button.clicked.connect(self.cancel_signal)
        self.buttonBox = QDialogButtonBox()
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.addButton(self.create_button,
                                 QDialogButtonBox.ActionRole)
        self.buttonBox.addButton(self.cancel_button,
                                 QDialogButtonBox.ActionRole)

        self.layout.removeItem(self.layout.itemAt(7))
        self.save_button.setParent(None)

        self.layout.addWidget(self.buttonBox)

    def create_signal(self):
        """
        Create a new profile with the given information in the LineEdit widgets. Saves to the OCI config file
        """
        profile_name = self.new_profile_name.text()
        self.config[profile_name] = {}
        self.config[profile_name]['tenancy'] = self.tenancy.text()
        self.config[profile_name]['region'] = self.region.text()
        self.config[profile_name]['user'] = self.user.text()
        self.config[profile_name]['fingerprint'] = self.fingerprint.text()
        self.config[profile_name]['key_file'] = self.key_file.text()
        self.config[profile_name]['pass_phrase'] = self.passphrase.text()

        with open(self.DEFAULT_LOCATION, 'w') as configfile:
            self.config.write(configfile)

        self.current_profile = profile_name
        self.cancel_signal()

    def save_signal(self):
        """
        Saves edits on a currently existing profile. Saves to the OCI config file
        """
        self.config[self.current_profile]['tenancy'] = self.tenancy.text()
        self.config[self.current_profile]['region'] = self.region.text()
        self.config[self.current_profile]['user'] = self.user.text()
        self.config[
            self.current_profile]['fingerprint'] = self.fingerprint.text()
        self.config[self.current_profile]['key_file'] = self.key_file.text()
        self.config[
            self.current_profile]['pass_phrase'] = self.passphrase.text()

        with open(self.DEFAULT_LOCATION, 'w') as configfile:
            self.config.write(configfile)

    def cancel_signal(self):
        """
        Cancels the creation a new profile and reverts layout to default layout
        """
        self.layout.removeItem(self.layout.itemAt(0))
        self.new_profile_name.setParent(None)

        self.dropdown = self.get_profiles_dropdown()
        self.layout.insertWidget(0, self.dropdown)

        self.layout.removeItem(self.layout.itemAt(7))
        self.buttonBox.setParent(None)

        self.change_profile(self.current_profile)
        self.dropdown.setCurrentText(self.current_profile)

        self.layout.addWidget(self.save_button)
class Widget(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        self.setWindowTitle("Backend Discord-GUI")
        self.changeStyle('fusion')

        palette = QPalette()
        palette.setColor(QPalette.Window, QColor(53, 53, 53))
        palette.setColor(QPalette.WindowText, Qt.white)
        palette.setColor(QPalette.Text, Qt.white)
        palette.setColor(QPalette.Button, QColor(60, 60, 60))
        palette.setColor(QPalette.ButtonText, Qt.white)
        palette.setColor(QPalette.Base, QColor(40, 40, 40))
        palette.setColor(QPalette.ToolTipBase, QColor(60, 60, 60))
        palette.setColor(QPalette.ToolTipText, Qt.white)
        palette.setColor(QPalette.PlaceholderText, Qt.white)
        palette.setColor(QPalette.BrightText, Qt.white)
        palette.setColor(QPalette.Highlight, QColor(106, 13, 173))
        palette.setColor(QPalette.HighlightedText, Qt.white)

        topButtonLayout = QGroupBox("Configurations")
        topStatsLayout = QGroupBox("Statistics")

        layoutLeft = QHBoxLayout()

        botConfigButton = QPushButton("Bot Config")
        botConfigButton.clicked.connect(lambda: CommentPopup())
        serverSettingsButton = QPushButton("Server Settings")
        settingsButton = QPushButton("Settings")

        layoutLeft.addWidget(botConfigButton)
        layoutLeft.addWidget(serverSettingsButton)
        layoutLeft.addWidget(settingsButton)

        layoutRight = QVBoxLayout()

        botReadyLabel = QLabel("Bot_Ready: False")
        botStatusLabel = QLabel("Bot_Status: Off")
        # botDatabaseLabel = QLabel("Bot_Database: None")
        # botStandbyLabel = QLabel("Bot_Standby: False")

        layoutRight.addWidget(botReadyLabel)
        layoutRight.addWidget(botStatusLabel)
        # layoutRight.addWidget(botDatabaseLabel)
        # layoutRight.addWidget(botStandbyLabel)

        topButtonLayout.setLayout(layoutLeft)
        topStatsLayout.setLayout(layoutRight)

        self.createLeftSide()
        self.createRightSide()
        self.createProgressBar()

        topLayout = QGridLayout()
        topLayout.addWidget(topButtonLayout, 0, 0)
        topLayout.addWidget(topStatsLayout, 0, 1)
        topLayout.setColumnStretch(0, 1)

        mainLayout = QGridLayout()
        mainLayout.addLayout(topLayout, 0, 0, 1, 2)
        mainLayout.addWidget(self.leftSideGB, 1, 0)
        mainLayout.addWidget(self.topRightGroupBox, 1, 1)
        mainLayout.addWidget(self.progressBar, 3, 0, 1, 2)
        mainLayout.setRowStretch(1, 2)
        mainLayout.setColumnStretch(0, 1)
        mainLayout.setColumnStretch(1, 2)
        self.setLayout(mainLayout)

        QApplication.setPalette(palette)

    def changeStyle(self, styleName):
        QApplication.setStyle(QStyleFactory.create(styleName))

    def advanceProgressBarLoading(self):
        curVal = self.progressBar.value()
        maxVal = self.progressBar.maximum()
        if curVal != maxVal:
            num = random.randint(1, 30)
            self.progressBar.setValue(curVal + num)
        else:
            self.timer.stop()
            change_status('Ready')
            self.progressBar.setValue(0)

    def createLeftSide(self):
        self.leftSideGB = QGroupBox()

        home_directory = "./app/"

        palette = QPalette()
        palette.setColor(QPalette.Window, QColor(30, 30, 30))

        model = QDirModel()
        view = QTreeView()
        view.setStyleSheet("QTreeView { border: 0px; }")
        view.setModel(model)
        view.setRootIndex(model.index(home_directory))
        view.setColumnHidden(1, True)
        view.setColumnHidden(2, True)
        view.setColumnHidden(3, True)
        view.show()
        view.setPalette(palette)

        runButton = QPushButton("►")
        stopButton = QPushButton("❚❚")

        bottomBar = QHBoxLayout()
        bottomBar.addWidget(runButton)
        bottomBar.addWidget(stopButton)

        layout = QVBoxLayout()
        layout.addWidget(view)
        layout.addLayout(bottomBar)
        layout.setStretch(0, 2)
        self.leftSideGB.setLayout(layout)

    def createRightSide(self):
        self.topRightGroupBox = QGroupBox()
        self.totalLength = 0
        self.elems = 0
        self.elems_list = []

        self.overall_layout = QVBoxLayout()

        grad = QPalette()
        gradient = QConicalGradient(QPointF(1100, 150), -190)
        gradient.setColorAt(0.0, QColor(30, 30, 30))
        gradient.setColorAt(0.5, QColor(50, 50, 50))
        gradient.setColorAt(0.97, QColor(50, 13, 150))
        gradient.setColorAt(1.0, QColor(106, 13, 173))
        gradient.setSpread(QGradient.RepeatSpread)
        grad.setBrush(QPalette.Window, QBrush(gradient))
        self.setPalette(grad)

        self.scrollarea = QScrollArea()
        self.scrollarea.setWidgetResizable(True)

        self.widget = QWidget()
        self.scrollarea.setWidget(self.widget)

        self.layout = QVBoxLayout(self.widget)

        self.add_elem = QPushButton("Add Element")
        if PLATFORM == "darwin": self.add_elem.setToolTip("Shortcut: ⌘E")
        else: self.add_elem.setToolTip("Shortcut: Ctrl+E")
        self.add_elem.setStyleSheet(
            "QToolTip { border: 0px; border-radius: 3px }")
        self.add_elem.clicked.connect(lambda: ElementPopup())
        self.add_elem.setFixedWidth(300)

        shortcut = QShortcut(QKeySequence("Ctrl+E"), self.add_elem)
        shortcut.activated.connect(lambda: ElementPopup())
        shortcut.setEnabled(True)

        self.layout.addWidget(self.add_elem)
        self.layout.setAlignment(self.add_elem, Qt.AlignCenter | Qt.AlignTop)
        self.overall_layout.addWidget(self.scrollarea)
        self.topRightGroupBox.setLayout(self.overall_layout)

    def add_element(self, title, type, isDupe=False, indexForDupe=0, data=""):
        # open form of widget lists
        if data != "": title = title + ": " + data
        elem = create_elem(title, type, data)
        self.elems_list.append(elem.getElem())
        self.elems += 1
        self.totalLength += 100

        if isDupe:
            self.layout.insertWidget(indexForDupe + 1,
                                     self.elems_list[self.elems - 1])
        else:
            self.layout.insertWidget(self.elems - 1,
                                     self.elems_list[self.elems - 1])

        if self.totalLength > self.topRightGroupBox.height():
            self.scrollarea.verticalScrollBar().setMaximum(
                self.scrollarea.verticalScrollBar().maximum() + 85)
            self.scrollarea.verticalScrollBar().setValue(
                self.scrollarea.verticalScrollBar().maximum())
        self.topRightGroupBox.update()

    def createProgressBar(self):
        self.progressBar = QProgressBar()
        self.progressBar.setRange(0, 10000)
        self.progressBar.setValue(0)
        self.progressBar.setTextVisible(False)
        self.progressBar.setFixedHeight(5)

        # self.timer = QTimer(self)
        # self.timer.timeout.connect(self.advanceProgressBarLoading)
        # self.timer.start(10)

    @Slot()
    def quit_application(self):
        QApplication.quit()
class NotificationsList(QWidget):
    READ_BACKGROUND_COLOR = "white"
    UNREAD_BACKGROUND_COLOR = "#eeeeee"
    WIDGET_BACKGROUND_COLOR = "darkGray"

    def __init__(self, dp, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)

        self._dp = dp
        self._items = []
        self._main_layout = QVBoxLayout(self)
        self._main_layout.setSpacing(2)
        self._main_layout.setContentsMargins(0, 0, 0, 0)
        self._main_layout.addStretch()
        self.setStyleSheet('background-color: {};'.format(
            self.READ_BACKGROUND_COLOR))

    def show_notifications(self, notifications):
        items_count = len(self._items)
        for i, notification in enumerate(notifications):
            if i < items_count:
                self._update_n_list_item_widget(notification, self._items[i])
            else:
                self._create_n_list_item_widget(notification)

        for i in range(items_count - 1, len(notifications) - 1, -1):
            self._main_layout.removeWidget(self._items[i])
            self._items.pop()
        self.setStyleSheet('background-color: {};'.format(
            self.WIDGET_BACKGROUND_COLOR))

    def loading_needed(self, limit):
        items_len = len(self._items)
        if items_len < limit:
            return True

        for widget in self._items[-limit:]:
            if not widget.visibleRegion().isEmpty():
                return True

        return False

    def _create_n_list_item_widget(self, notification):
        widget = QWidget(parent=self)
        # widget.setFixedWidth(self.width())
        widget.notification = notification

        main_layout = QVBoxLayout(widget)
        main_layout.setSpacing(2)

        text_label = QLabel(widget)
        widget.text_label = text_label
        text_label.setWordWrap(True)
        text_label.setFont(QFont('Noto Sans', 10 * self._dp))
        text_label.setAlignment(Qt.AlignTop | Qt.AlignLeft)
        text_label.setText(notification.get_text())
        main_layout.addWidget(text_label)

        time_label = QLabel(widget)
        widget.time_label = time_label
        time_label.setFont(QFont('Noto Sans', 8 * self._dp))
        time_label.setAlignment(Qt.AlignTop | Qt.AlignRight)
        time_label.setText(notification.get_datetime())
        main_layout.addWidget(time_label)

        self._set_background_color(widget, notification)

        def clicked(_):
            widget.notification.read()
            self._set_background_color(widget, widget.notification)

        widget.mouseReleaseEvent = clicked
        widget.text_label.mouseReleaseEvent = clicked
        widget.time_label.mouseReleaseEvent = clicked
        self._main_layout.insertWidget(len(self._items), widget)
        self._items.append(widget)

    def _update_n_list_item_widget(self, notification, widget):
        widget.notification = notification
        widget.text_label.setText(notification.get_text())
        widget.time_label.setText(notification.get_datetime())
        self._set_background_color(widget, notification)

    def _set_background_color(self, widget, notification):
        background_color = self.READ_BACKGROUND_COLOR \
            if notification.is_read \
            else self.UNREAD_BACKGROUND_COLOR
        widget.setStyleSheet('background-color: {};'.format(background_color))
Exemple #21
0
class ManagerWindow(MayaQWidgetDockableMixin, QWidget):
    add_spore_clicked = Signal(str)
    remove_spore_clicked = Signal()
    refresh_spore_clicked = Signal()
    close_event = Signal()

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

        self.setWindowTitle('Spore Manager')
        self.setGeometry(50, 50, 400, 550)
        #  self.setMinimumSize(350, 400)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

        self.items = []  # list of all item widgets

        self.build_ui()
        self.connect_signals()

    def build_ui(self):
        layout = QGridLayout(self)
        layout.setContentsMargins(5, 5, 5, 5)
        self.setLayout(layout)

        self.name_edt = QLineEdit()
        self.name_edt.setPlaceholderText('Create New')
        self.name_edt.setSizePolicy(QSizePolicy.Preferred,
                                    QSizePolicy.Preferred)
        layout.addWidget(self.name_edt, 0, 0, 1, 1)

        self.add_btn = QPushButton()
        self.add_btn.setIcon(QIcon(QPixmap(':/teAdditive.png')))
        self.add_btn.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
        layout.addWidget(self.add_btn, 0, 1, 1, 1)

        self.refresh_btn = QPushButton()
        self.refresh_btn.setIcon(QIcon(QPixmap(':/teKeyRefresh.png')))
        self.refresh_btn.setSizePolicy(QSizePolicy.Maximum,
                                       QSizePolicy.Preferred)
        layout.addWidget(self.refresh_btn, 0, 2, 1, 1)

        scroll_wdg = QWidget(self)
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setStyleSheet(
            "QScrollArea { background-color: rgb(57,57,57);}")
        scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scroll_area.setWidget(scroll_wdg)

        self.spore_layout = QVBoxLayout()
        self.spore_layout.setContentsMargins(1, 1, 3, 1)
        self.spore_layout.setSpacing(0)
        self.spore_layout.addStretch()
        scroll_wdg.setLayout(self.spore_layout)
        layout.addWidget(scroll_area, 1, 0, 1, 3)

        #  self.frame_lay.addWidget(ItemWidget())
        #  layout.addWidget(btn, 0, 0, 1, 1)

    def connect_signals(self):
        self.name_edt.returnPressed.connect(
            lambda: self.add_spore_clicked.emit(self.name_edt.text()))
        self.add_btn.clicked.connect(
            lambda: self.add_spore_clicked.emit(self.name_edt.text()))
        #  self.remove_btn.clicked.connect(self.remove_spore_clicked.emit)
        self.refresh_btn.clicked.connect(self.refresh_spore_clicked.emit)

    def append_item(self, item):
        self.items.append(item)
        self.spore_layout.insertWidget(0, item)

    def remove_item(self, item):
        pass

    #  def clear_items(self):
    #      for item in self.items:
    #          self.spore_layout.removeWidget(item)
    #          self.items.remove(item)
    #          item.delateLater()
    #          del item
    #
    #      self.spore_layout.update()

    def clear_layout(self):
        """ remove all child widgets and layout """

        self.name_edt.setText('')

        del self.items[:]
        while self.spore_layout.count():
            child = self.spore_layout.takeAt(0)
            if child.widget() is not None:
                child.widget().deleteLater()
            #  elif child.layout() is not None:
            #      self.clear_layout(child.layout())

        self.spore_layout.setSpacing(0)
        self.spore_layout.addStretch()

    def closeEvent(self, event):
        self.close_event.emit()

    def hideEvent(self, event):
        self.close_event.emit()
Exemple #22
0
class ComponentTreeViewTab(QWidget):
    def __init__(self, scene_widget: InstrumentView, parent=None):
        super().__init__()
        self.setLayout(QVBoxLayout())
        self.setParent(parent)
        self.componentsTabLayout = QVBoxLayout()
        self.component_tree_view = QNexusTreeView()
        self.parameters_widget = ParametersView(parent)
        self.componentsTabLayout.addWidget(self.parameters_widget)
        self.componentsTabLayout.addWidget(self.component_tree_view)

        self.layout().addLayout(self.componentsTabLayout)

        self.sceneWidget = scene_widget

        self.component_tree_view.setDragEnabled(True)
        self.component_tree_view.setAcceptDrops(True)
        self.component_tree_view.setDropIndicatorShown(True)
        self.component_tree_view.header().hide()
        self.component_tree_view.updateEditorGeometries()
        self.component_tree_view.updateGeometries()
        self.component_tree_view.updateGeometry()
        self.component_tree_view.clicked.connect(self._set_button_state)
        self.component_tree_view.setSelectionMode(
            QAbstractItemView.SingleSelection)

        self.component_tool_bar = QToolBar("Actions", self)
        self.new_component_action = create_and_add_toolbar_action(
            "new_component.png",
            "Group",
            self.parent().show_add_component_dialog,
            self.component_tool_bar,
            self,
            False,
        )
        self.new_translation_action = create_and_add_toolbar_action(
            "new_translation.png",
            "Translation",
            lambda: self._add_transformation(TransformationType.TRANSLATION),
            self.component_tool_bar,
            self,
        )
        self.new_rotation_action = create_and_add_toolbar_action(
            "new_rotation.png",
            "Rotation",
            lambda: self._add_transformation(TransformationType.ROTATION),
            self.component_tool_bar,
            self,
        )
        self.create_link_action = create_and_add_toolbar_action(
            "create_link.png",
            "Link",
            self.on_create_link,
            self.component_tool_bar,
            self,
        )
        self.edit_component_action = create_and_add_toolbar_action(
            "edit_component.png",
            "Edit",
            self.parent().show_edit_component_dialog,
            self.component_tool_bar,
            self,
        )
        self.zoom_action = create_and_add_toolbar_action(
            "zoom.svg",
            "Zoom",
            self.on_zoom_item,
            self.component_tool_bar,
            self,
        )
        self.component_tool_bar.insertSeparator(self.zoom_action)

        self.spacer = QWidget()
        self.spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.component_tool_bar.addWidget(self.spacer)
        self.delete_action = create_and_add_toolbar_action(
            "delete.png", "Delete", self.on_delete_item,
            self.component_tool_bar, self)
        self.component_tool_bar.insertSeparator(self.delete_action)
        self.componentsTabLayout.insertWidget(0, self.component_tool_bar)

    def set_up_model(self, model: Model):
        model.signals.group_edited.connect(
            self.component_tree_view.collapse_group_in_tree)
        self.component_model = NexusTreeModel(model)
        self.component_delegate = ComponentEditorDelegate(
            self.component_tree_view, model)
        self.component_tree_view.setItemDelegate(self.component_delegate)
        self.component_tree_view.setModel(self.component_model)
        self.parameters_widget.set_up_model(model)

    def reset_model(self):
        self.set_up_model(self.component_model.model)

    def _set_button_state(self):
        set_button_states(
            self.component_tree_view,
            self.new_component_action,
            self.delete_action,
            self.new_rotation_action,
            self.new_translation_action,
            self.create_link_action,
            self.zoom_action,
            self.edit_component_action,
        )

    def on_create_link(self):
        selected = self.component_tree_view.selectedIndexes()
        if len(selected) > 0:
            self.component_model.add_link(selected[0])
            self._expand_transformation_list(selected[0])
            self._set_button_state()

    def _expand_transformation_list(self, node: QModelIndex):
        expand_transformation_list(node, self.component_tree_view,
                                   self.component_model)

    def _add_transformation(self, transformation_type: str):
        add_transformation(transformation_type, self.component_tree_view,
                           self.component_model)
        self._set_button_state()

    def on_delete_item(self):
        selected = self.component_tree_view.selectedIndexes()
        if len(selected[0].data().parent_node.children) == 1:
            new_selection_index = selected[0].parent()
        elif selected[0].row() > 0:
            new_selection_index = self.component_model.parent(
                selected[0]).child(selected[0].row() - 1, 0)
        elif selected[-1].row() <= len(
                selected[-1].data().parent_node.children) - 1:
            new_selection_index = self.component_model.parent(
                selected[-1]).child(selected[-1].row(), 0)
        else:
            new_selection_index = selected[0].parent()
        for item in selected:
            self.component_model.remove_node(item)
        self.component_tree_view.setCurrentIndex(new_selection_index)
        self._set_button_state()

    def on_zoom_item(self):
        selected = self.component_tree_view.selectedIndexes()[0]
        component = selected.internalPointer()
        self.sceneWidget.zoom_to_component(
            self.sceneWidget.get_entity(component.name),
            self.sceneWidget.view.camera())
Exemple #23
0
class ButtonList(QWidget):

    # 按钮添加时发送信号
    # QPushButton为触发的按钮
    signalBtnAdded = Signal(QPushButton)
    signalBtnDeleted = Signal(QPushButton)
    signalBtnClicked = Signal(QPushButton)
    """按钮列表,默认有一个添加按钮的按钮"""
    def __init__(self, addStr: str, parent: any = None) -> None:
        '''按钮列表,默认有一个添加按钮的按钮

        Args:
            addStr(str):添加按钮上显示的文字
        '''
        super().__init__(parent=parent)

        self.addIterm = QPushButton(addStr, self)
        self.addIterm.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        # self.addIterm.resize(self.addIterm.sizeHint())
        self.addIterm.clicked.connect(self.addBtn_clicked)

        # 添加按钮布局
        self.layDown = QVBoxLayout()
        self.layDown.addWidget(self.addIterm)
        # 按钮布局,与addIterm所处的布局分开,防止删除按钮时布局的神秘错乱(我太菜了)
        self.layUp = QVBoxLayout()
        # 整体外布局
        lay = QVBoxLayout()
        lay.setMargin(3)  # 设置边距
        lay.addLayout(self.layUp)
        lay.addLayout(self.layDown)
        lay.addStretch(1)  # 添加拉伸因子,防止按钮由于父控件大小被纵向拉伸
        self.setLayout(lay)  # 应用布局

    @Slot()
    def addBtn_clicked(self) -> QPushButton:
        index = self.layUp.count()  # 添加的按钮在布局中的索引位置,起始0
        button = QPushButton('btn{}'.format(index))
        button.clicked.connect(lambda: self.button_clicked(button))

        button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

        def on_context_menu(point):
            # 弹出菜单
            menu.exec_(button.mapToGlobal(point))  # 把相对于按钮的位置转为相对于全局的位置

        button.setContextMenuPolicy(Qt.CustomContextMenu)  # 菜单策略,自定义
        button.customContextMenuRequested.connect(on_context_menu)  # 触发信号

        # 设置右击删除菜单
        menu = QMenu(button)
        delQAction = QAction("删除", button)
        delQAction.triggered.connect(lambda: self.deleteButton(button))
        menu.addAction(delQAction)

        self.layUp.insertWidget(index, button)  # 添加按钮到布局中
        self.signalBtnAdded.emit(button)  # 发送添加信号
        return button

    @Slot(QPushButton)
    def deleteButton(self, button: QPushButton):
        self.signalBtnDeleted.emit(button)  # 发送删除信号
        # self.layUp.removeWidget(button)  # 移除控件
        button.deleteLater()  # 删除控件

    @Slot(QPushButton)
    def button_clicked(self, button: QPushButton):
        self.signalBtnClicked.emit(button)  # 发送点击信号