Example #1
0
    def __init__(self):
        super(FindToolBar, self).__init__()
        self._line_edit = QLineEdit()
        self._line_edit.setClearButtonEnabled(True)
        self._line_edit.setPlaceholderText("Find...")
        self._line_edit.setMaximumWidth(300)
        self._line_edit.returnPressed.connect(self._find_next)
        self.addWidget(self._line_edit)

        self._previous_button = QToolButton()
        self._previous_button.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/up-32.png'))
        self._previous_button.clicked.connect(self._find_previous)
        self.addWidget(self._previous_button)

        self._next_button = QToolButton()
        self._next_button.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/down-32.png'))
        self._next_button.clicked.connect(self._find_next)
        self.addWidget(self._next_button)

        self._case_sensitive_checkbox = QCheckBox('Case Sensitive')
        self.addWidget(self._case_sensitive_checkbox)

        self._hideButton = QToolButton()
        self._hideButton.setShortcut(QKeySequence(Qt.Key_Escape))
        self._hideButton.setIcon(QIcon(':/qt-project.org/styles/macstyle/images/closedock-16.png'))
        self._hideButton.clicked.connect(self.hide)
        self.addWidget(self._hideButton)
Example #2
0
class FindToolBar(QToolBar):

    find = QtCore.Signal(str, QWebEnginePage.FindFlags)

    def __init__(self):
        super(FindToolBar, self).__init__()
        self._line_edit = QLineEdit()
        self._line_edit.setClearButtonEnabled(True)
        self._line_edit.setPlaceholderText("Find...")
        self._line_edit.setMaximumWidth(300)
        self._line_edit.returnPressed.connect(self._find_next)
        self.addWidget(self._line_edit)

        self._previous_button = QToolButton()
        self._previous_button.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/up-32.png'))
        self._previous_button.clicked.connect(self._find_previous)
        self.addWidget(self._previous_button)

        self._next_button = QToolButton()
        self._next_button.setIcon(QIcon(':/qt-project.org/styles/commonstyle/images/down-32.png'))
        self._next_button.clicked.connect(self._find_next)
        self.addWidget(self._next_button)

        self._case_sensitive_checkbox = QCheckBox('Case Sensitive')
        self.addWidget(self._case_sensitive_checkbox)

        self._hideButton = QToolButton()
        self._hideButton.setShortcut(QKeySequence(Qt.Key_Escape))
        self._hideButton.setIcon(QIcon(':/qt-project.org/styles/macstyle/images/closedock-16.png'))
        self._hideButton.clicked.connect(self.hide)
        self.addWidget(self._hideButton)

    def focus_find(self):
        self._line_edit.setFocus()

    def _emit_find(self, backward):
        needle =  self._line_edit.text().strip()
        if needle:
            flags = QWebEnginePage.FindFlags()
            if self._case_sensitive_checkbox.isChecked():
                flags |= QWebEnginePage.FindCaseSensitively
            if backward:
                flags |= QWebEnginePage.FindBackward
            self.find.emit(needle, flags)

    def _find_next(self):
        self._emit_find(False)

    def _find_previous(self):
        self._emit_find(True)
Example #3
0
class TableWidget(QWidget):
    def __init__(self,
                 table,
                 headers,
                 bold=True,
                 mono=True,
                 tooltips=None,
                 align=False,
                 search=True,
                 parent=None):
        super(TableWidget, self).__init__(parent)

        self.table_widget = QTableWidget(len(table), len(table[0]))
        for i, row in enumerate(table):
            for j, item in enumerate(row):
                if item is not None:
                    self.table_widget.setItem(i, j,
                                              QTableWidgetItem(str(item)))
                    if tooltips is not None:
                        self.table_widget.setToolTip(tooltips[i][j])
                    modify_font(self.table_widget.item(i, j),
                                bold=bold and j == 0,
                                mono=mono)
                    if align:
                        self.table_widget.item(i, j).setTextAlignment(
                            Qt.AlignRight)

        self.table_headers = headers
        self.table_widget.setHorizontalHeaderLabels(self.table_headers)
        self.table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_widget.resizeColumnsToContents()
        self.table_widget.setAlternatingRowColors(True)
        self.table_widget.itemDoubleClicked.connect(self.copy)

        search_layout = QHBoxLayout()
        search_layout.addWidget(QLabel(self.tr('Search:')))
        self.search_edit = QLineEdit()
        self.search_edit.textChanged.connect(self.start)
        self.search_edit.returnPressed.connect(self.next)
        search_layout.addWidget(self.search_edit)

        clear_button = QToolButton()
        clear_button.setIcon(QIcon('icons/clear.svg'))
        clear_button.setShortcut(QKeySequence.DeleteCompleteLine)
        clear_button.setToolTip(self.tr('Clear pattern'))
        clear_button.clicked.connect(self.search_edit.clear)
        search_layout.addWidget(clear_button)

        self.case_button = QToolButton()
        self.case_button.setText(self.tr('Aa'))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(self.start)
        self.case_button.setToolTip(self.tr('Case sensitive'))
        search_layout.addWidget(self.case_button)

        self.word_button = QToolButton()
        self.word_button.setText(self.tr('W'))
        self.word_button.setCheckable(True)
        self.word_button.toggled.connect(self.start)
        self.word_button.setToolTip(self.tr('Whole words'))
        search_layout.addWidget(self.word_button)

        self.regex_button = QToolButton()
        self.regex_button.setText(self.tr('.*'))
        self.regex_button.setCheckable(True)
        self.regex_button.toggled.connect(self.start)
        self.regex_button.setToolTip(self.tr('Regular expression'))
        search_layout.addWidget(self.regex_button)

        prev_button = QToolButton()
        prev_button.setIcon(QIcon('icons/up.svg'))
        prev_button.setShortcut(QKeySequence.FindPrevious)
        prev_button.clicked.connect(self.previous)
        prev_button.setToolTip(self.tr('Previous occurence'))
        search_layout.addWidget(prev_button)

        next_button = QToolButton()
        next_button.setIcon(QIcon('icons/down.svg'))
        next_button.setShortcut(QKeySequence.FindNext)
        next_button.clicked.connect(self.next)
        next_button.setToolTip(self.tr('Next occurence'))
        search_layout.addWidget(next_button)

        self.matches_label = QLabel()
        search_layout.addWidget(self.matches_label)
        search_layout.addStretch()

        export_button = QToolButton()
        export_button.setText(self.tr('Export...'))
        export_button.clicked.connect(self.export)
        search_layout.addWidget(export_button)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.table_widget)
        if search:
            main_layout.addLayout(search_layout)
        self.setLayout(main_layout)

    def start(self):
        self.search(self.search_edit.text(), -1, -1, 1)

    def next(self):
        row = self.table_widget.currentRow()
        col = self.table_widget.currentColumn() + 1
        if col == self.table_widget.columnCount():
            row += 1
            col = 0
        self.search(self.search_edit.text(), row, col, +1)

    def previous(self):
        row = self.table_widget.currentRow()
        col = self.table_widget.currentColumn() - 1
        if col == -1:
            row -= 1
            col = self.table_widget.columnCount() - 1
        self.search(self.search_edit.text(), row, col, -1)

    def search(self, pattern, row, col, direction):
        nocase = not self.case_button.isChecked()
        word = self.word_button.isChecked()
        regex = self.regex_button.isChecked()
        matches = 0
        index = 0
        if direction > 0:
            row_range = range(self.table_widget.rowCount() - 1, -1, -1)
            col_range = range(self.table_widget.columnCount() - 1, -1, -1)
        else:
            row_range = range(self.table_widget.rowCount())
            col_range = range(self.table_widget.columnCount())
        for i in row_range:
            for j in col_range:
                item = self.table_widget.item(i, j)
                if item is not None:
                    text = item.text()
                    if regex:
                        match = QRegularExpression(pattern).match(
                            text).hasMatch()
                    else:
                        if nocase:
                            text = text.lower()
                            pattern = pattern.lower()
                        if word:
                            match = text == pattern
                        else:
                            match = pattern in text
                    if match and pattern:
                        self.table_widget.item(i, j).setBackground(Qt.yellow)
                        if (direction > 0 and (i > row or i == row and j > col)) or \
                                (direction < 0 and (i < row or i == row and j < col)):
                            self.table_widget.setCurrentCell(i, j)
                            index = matches
                        matches += 1
                    else:
                        self.table_widget.item(i,
                                               j).setBackground(Qt.transparent)
        if pattern:
            self.matches_label.setVisible(True)
            if matches > 0:
                match = matches - index if direction > 0 else index + 1
                self.matches_label.setText(
                    self.tr('match #{}/{}'.format(match, matches)))
                self.matches_label.setStyleSheet('color: #000000')
                modify_font(self.matches_label, bold=True)
            else:
                self.matches_label.setText(self.tr('not found!'))
                self.matches_label.setStyleSheet('color: #FF0000')
                modify_font(self.matches_label, italic=True)
        else:
            self.matches_label.setText('')

    def export(self):
        settings = QSettings()
        filename = QFileDialog.getSaveFileName(self,
                                               self.tr('Export metadata'),
                                               settings.value('save_folder'),
                                               self.tr('CSV files (*.csv)'))[0]
        if not filename:
            return
        if not filename.endswith('.csv'):
            filename += '.csv'
        settings.setValue('save_folder', QFileInfo(filename).absolutePath())

        rows = self.table_widget.rowCount()
        cols = self.table_widget.columnCount()
        table = [[None for _ in range(cols)] for __ in range(rows)]
        for i in range(rows):
            for j in range(cols):
                item = self.table_widget.item(i, j)
                if item is not None:
                    table[i][j] = item.text()
        with open(filename, 'w') as file:
            writer = csv.writer(file)
            writer.writerow(self.table_headers)
            writer.writerows(table)
        self.info_message.emit(self.tr('Table contents exported to disk'))

    def copy(self, item):
        QApplication.clipboard().setText(item.text())
        QToolTip.showText(QCursor.pos(),
                          self.tr('Cell contents copied to clipboard'), self,
                          QRect(), 3000)
Example #4
0
class SoundBoard(QDialog):
    def __init__(self):
        super(SoundBoard, self).__init__()
        self.title = '=== SoundBoard ==='
        # positionnement de la fenêtre à l'ouverture
        self.left = 50
        self.top = 50
        # initialisation de la largeur et hauteur par défaut
        self.width = 500
        self.height = 500
        self.currFileName = ""
        self.pbPosToModify = -1
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.windowLayout = QHBoxLayout()
        self.tableWidget = QTableWidget()
        self.tableWidget.horizontalHeader().hide()
        self.tableWidget.verticalHeader().hide()
        self.initIcons()
        self.initMenu()
        self.initColorPicker()
        self.initButtons()
        self.windowLayout.setStretch(1, 0)
        self.setLayout(self.windowLayout)
        self.show()

    def initIcons(self):
        self.iEdit = QIcon()
        self.iEdit.addPixmap(QPixmap("./icons/edit.png"), QIcon.Normal,
                             QIcon.Off)

        self.iPlus = QIcon()
        self.iPlus.addPixmap(QPixmap("./icons/plus.png"), QIcon.Normal,
                             QIcon.Off)

        self.iMinus = QIcon()
        self.iMinus.addPixmap(QPixmap("./icons/minus.png"), QIcon.Normal,
                              QIcon.Off)

        self.iParam = QIcon()
        self.iParam.addPixmap(QPixmap("./icons/cog.png"), QIcon.Normal,
                              QIcon.Off)

    def initMenu(self):
        layout = QVBoxLayout()
        hlayout = QHBoxLayout()

        # bouton ajout
        self.tbPlus = QToolButton()
        self.tbPlus.setGeometry(QRect(0, 0, 32, 32))
        self.tbPlus.setIcon(self.iPlus)
        self.tbPlus.setObjectName("tbPlus")

        hlayout.addWidget(self.tbPlus)
        self.tbPlus.clicked.connect(self.add)

        # bouton suppression
        self.tbMinus = QToolButton()
        self.tbMinus.setGeometry(QRect(0, 0, 32, 32))
        self.tbMinus.setIcon(self.iMinus)
        self.tbMinus.setObjectName("tbMinus")

        hlayout.addWidget(self.tbMinus)
        self.tbMinus.clicked.connect(self.delete)

        # bouton édition
        self.tbEdit = QToolButton()
        self.tbEdit.setGeometry(QRect(0, 0, 32, 32))
        self.tbEdit.setIcon(self.iEdit)
        self.tbEdit.setObjectName("tbEdit")

        hlayout.addWidget(self.tbEdit)
        self.tbEdit.clicked.connect(self.editBtn)

        # bouton paramètres
        self.tbParam = QToolButton()
        self.tbParam.setGeometry(QRect(0, 0, 32, 32))
        self.tbParam.setIcon(self.iParam)
        self.tbParam.setObjectName("tbParam")

        hlayout.addWidget(self.tbParam)
        self.tbParam.clicked.connect(self.settings)

        layout.addLayout(hlayout)

        self.pbStop = QPushButton("Don't STOP\n\nthe\n\nSoundBoard")
        self.pbStop.setStyleSheet("font-weight: bold;")
        self.pbStop.setMinimumSize(QSize(100, 100))
        self.pbStop.setGeometry(QRect(0, 0, 100, 100))
        layout.addWidget(self.pbStop)
        self.pbStop.clicked.connect(self.stop)

        spacerMenu = QSpacerItem(20, 40, QSizePolicy.Minimum,
                                 QSizePolicy.Expanding)
        layout.addItem(spacerMenu)

        self.windowLayout.addLayout(layout)

    def startInitButtons(self):
        self.tableWidget.clear()
        self.tableWidget.clearSpans()
        self.tableWidget.setColumnWidth(0, 100)
        self.tableWidget.setColumnWidth(2, 100)
        self.cdColorPicker.setVisible(False)

        self.tableWidget.horizontalHeader().hide()
        # import des informations boutons contenues dans le json
        with open('buttons.json', encoding='utf-8') as json_file:
            self.data_buttons = json.load(json_file)

        # stockage de la position la plus élevée pour le cadrage
        self.positions = [p['position'] for p in self.data_buttons['buttons']]
        self.max_pos = max(self.positions)

        # calcul du nombre de boutons par hauteur et largeur
        self.BtnH = self.data_buttons['buttons_grid']['height']
        self.BtnW = self.data_buttons['buttons_grid']['width']
        self.setGeometry(self.left, self.top, 140 + self.BtnW * 100,
                         175 if self.BtnH * 31 < 175 else 25 + self.BtnH * 30)
        self.tableWidget.setColumnCount(self.BtnW)
        self.tableWidget.setRowCount(self.BtnH)

    def endInitButtons(self):
        buttonsLayout = QVBoxLayout()
        buttonsLayout.setStretch(0, 1)
        buttonsLayout.addWidget(self.tableWidget)

        self.windowLayout.addLayout(buttonsLayout)

        self.setGeometry(self.left, self.top, 140 + self.BtnW * 100,
                         175 if self.BtnH * 31 < 175 else 25 + self.BtnH * 30)

    def initButtons(self):
        self.startInitButtons()

        # positionnement des boutons en fonction des positions du json
        for ligne in range(self.BtnH):
            for colonne in range(self.BtnW):
                if (ligne * self.BtnW) + (colonne + 1) in self.positions:
                    for b in self.data_buttons['buttons']:
                        if b['position'] == (ligne * self.BtnW) + (colonne +
                                                                   1):
                            pb = QPushButton(b['name'][:9])
                            pb.setProperty('pbPos', b['position'])
                            # si fond clair, font noire, si sombre, font blanche
                            if (b['r'] * 0.299 + b['g'] * 0.587 +
                                    b['b'] * 0.114) > 186:
                                pb.setStyleSheet(
                                    f"background-color: rgb({b['r']},{b['g']},{b['b']}); color: #000000;"
                                )
                            else:
                                pb.setStyleSheet(
                                    f"background-color: rgb({b['r']},{b['g']},{b['b']}); color: #ffffff;"
                                )
                            self.tableWidget.setCellWidget(ligne, colonne, pb)
                            pb.clicked.connect(self.play)
                else:
                    pb = QPushButton('Nouveau')
                    calcPos = self.BtnW * ligne + colonne + 1
                    pb.setProperty('pbPos', f"nouveau,{calcPos}")
                    pb.clicked.connect(self.add)
                    self.tableWidget.setCellWidget(ligne, colonne, pb)
                colonne += 1
            ligne += 1

        self.endInitButtons()

    def initColorPicker(self):
        self.lColorPicker = QVBoxLayout()
        self.cdColorPicker = QColorDialog()
        self.cdColorPicker.setOption(self.cdColorPicker.NoButtons, True)
        self.colorSelected = self.cdColorPicker.currentColor()

        self.lColorPicker.addWidget(self.cdColorPicker)
        self.cdColorPicker.setVisible(False)
        self.cdColorPicker.currentColorChanged.connect(self.colorChanged)

        self.windowLayout.addLayout(self.lColorPicker)

    def play(self):
        pb = self.sender()
        pbPos = pb.property('pbPos')
        for b in self.data_buttons['buttons']:
            if pbPos == b['position']:
                pbFile = b['file']
        if (p.get_state() == vlc.State.Playing):
            p.stop()
            media = instance.media_new(soundRep + pbFile)
            if (self.currFileName != pbFile):
                p.set_media(media)
                p.play()
                self.currFileName = pbFile
        else:
            media = instance.media_new(soundRep + pbFile)
            p.set_media(media)
            p.play()
            self.currFileName = pbFile

    def stop(self):
        p.stop()

    def add(self):
        self.cdColorPicker.setVisible(True)
        self.tableWidget.clear()
        self.tableWidget.clearSpans()
        self.tableWidget.setColumnWidth(2, 100)

        self.tableWidget.setColumnCount(6)
        self.tableWidget.setRowCount(len(self.data_buttons['buttons']) + 1)

        self.tableWidget.horizontalHeader().show()

        self.tableWidget.setHorizontalHeaderItem(0, QTableWidgetItem())
        self.tableWidget.horizontalHeaderItem(0).setText('Nom')
        self.tableWidget.setHorizontalHeaderItem(1, QTableWidgetItem())
        self.tableWidget.horizontalHeaderItem(1).setText('Fichier')
        self.tableWidget.setHorizontalHeaderItem(2, QTableWidgetItem())
        self.tableWidget.horizontalHeaderItem(2).setText('')
        self.tableWidget.setColumnWidth(2, 22)
        self.tableWidget.setHorizontalHeaderItem(3, QTableWidgetItem())
        self.tableWidget.horizontalHeaderItem(3).setText('Position')
        self.tableWidget.setHorizontalHeaderItem(4, QTableWidgetItem())
        self.tableWidget.horizontalHeaderItem(4).setText('Couleur')
        self.tableWidget.setHorizontalHeaderItem(5, QTableWidgetItem())
        self.tableWidget.horizontalHeaderItem(5).setText('')

        # nom
        self.leName = QLineEdit()
        self.leName.setPlaceholderText('Nom (10 max.)')
        self.tableWidget.setCellWidget(0, 0, self.leName)
        # fichier
        self.leFile = QLineEdit()
        self.leFile.setPlaceholderText('Fichier')
        self.tableWidget.setCellWidget(0, 1, self.leFile)
        # browse
        pbBrowser = QPushButton('...')
        pbBrowser.setMinimumSize(QSize(21, 21))
        pbBrowser.clicked.connect(self.browseMedia)
        self.tableWidget.setCellWidget(0, 2, pbBrowser)
        # position
        self.lePos = QLineEdit()
        self.lePos.setPlaceholderText('Position')
        self.tableWidget.setCellWidget(0, 3, self.lePos)
        # couleur
        self.leColor = QLineEdit()
        self.leColor.setPlaceholderText('255,255,255')
        self.leColor.setText(
            str(self.colorSelected.red()) + "," +
            str(self.colorSelected.green()) + "," +
            str(self.colorSelected.blue()))
        self.tableWidget.setCellWidget(0, 4, self.leColor)
        # validation
        pbValid = QPushButton('Valider')
        pbValid.clicked.connect(self.addValid)
        self.tableWidget.setCellWidget(0, 5, pbValid)

        pb = self.sender()
        pbPos = pb.property('pbPos')
        if pbPos is not None:
            if str(pbPos)[:8] == 'nouveau,':
                self.lePos.setText(pbPos[8:])

        def sortByPos(val):
            return val['position']

        self.data_buttons['buttons'].sort(key=sortByPos)
        for ligne, b in enumerate(self.data_buttons['buttons'], start=1):
            self.tableWidget.setSpan(ligne, 1, 1, 2)
            self.tableWidget.setCellWidget(ligne, 0, QLabel(b['name']))
            self.tableWidget.setCellWidget(ligne, 1, QLabel(b['file']))
            self.tableWidget.setCellWidget(ligne, 3,
                                           QLabel(str(b['position'])))
            self.tableWidget.setCellWidget(ligne, 4, QLabel('Couleur'))

        # 530 color picker width
        self.setGeometry(self.left, self.top, 690 + 530, 300)

    def addValid(self):
        gName = self.leName.text()
        self.leName.setStyleSheet("color: rgb(0,0,0);")
        gFile = self.leFile.text()
        self.leFile.setStyleSheet("color: rgb(0,0,0);")
        gPos = self.lePos.text()
        self.lePos.setStyleSheet("color: rgb(0,0,0);")
        gColor = self.leColor.text()
        self.leColor.setStyleSheet("color: rgb(0,0,0);")
        # si champs vides
        if ((gName == '' or gName == 'Obligatoire !')
                or (gFile == '' or gFile == 'Obligatoire !')
                or (gPos == '' or gColor == 'Obligatoire !')
                or (gColor == '' or gColor == 'Obligatoire !')):
            if gName == '' or gName == 'Obligatoire !':
                self.leName.setText('Obligatoire !')
                self.leName.setStyleSheet(
                    "color: rgb(255,0,0); font-weight: bold;")
            if gFile == '' or gFile == 'Obligatoire !':
                self.leFile.setText('Obligatoire !')
                self.leFile.setStyleSheet(
                    "color: rgb(255,0,0); font-weight: bold;")
            if gPos == '' or gColor == 'Obligatoire !':
                self.lePos.setText('Obligatoire !')
                self.lePos.setStyleSheet(
                    "color: rgb(255,0,0); font-weight: bold;")
            if gColor == '' or gColor == 'Obligatoire !':
                self.leColor.setText('Obligatoire !')
                self.leColor.setStyleSheet(
                    "color: rgb(255,0,0); font-weight: bold;")
        else:
            # vérif si champ position est un nombre
            try:
                flag = 0
                flag = int(gPos)
            except ValueError:
                self.lePos.setText(f"{str(gPos)} n'est pas un nombre")
                self.lePos.setStyleSheet(
                    "color: rgb(255,0,0); font-weight: bold;")
            # si position est un nombre
            if flag != 0:
                # si position hors grille
                if int(gPos) < 0 or int(gPos) > self.data_buttons[
                        'buttons_grid']['height'] * self.data_buttons[
                            'buttons_grid']['width']:
                    self.lePos.setText(f"{str(gPos)} hors grille")
                    self.lePos.setStyleSheet(
                        "color: rgb(255,0,0); font-weight: bold;")
                else:
                    dictToAppend = {
                        "name": gName,
                        "file": gFile,
                        "position": int(gPos),
                        "r": self.colorSelected.red(),
                        "g": self.colorSelected.green(),
                        "b": self.colorSelected.blue()
                    }
                    # si c'est une modification
                    if self.pbPosToModify != -1:
                        for b in self.data_buttons['buttons']:
                            if b['position'] == self.pbPosToModify:
                                self.data_buttons['buttons'].remove(b)
                        self.data_buttons['buttons'].append(dictToAppend)
                        with open('buttons.json', 'w',
                                  encoding='utf-8') as outfile:
                            json.dump(self.data_buttons, outfile, indent=4)
                        self.initButtons()
                    else:
                        # si position déjà prise
                        if int(gPos) in self.positions:
                            self.lePos.setText(f"{str(gPos)} déjà prise")
                            self.lePos.setStyleSheet(
                                "color: rgb(255,0,0); font-weight: bold;")
                        else:
                            self.data_buttons['buttons'].append(dictToAppend)
                            with open('buttons.json', 'w',
                                      encoding='utf-8') as outfile:
                                json.dump(self.data_buttons, outfile, indent=4)
                            self.initButtons()

    def delete(self):
        self.startInitButtons()

        # positionnement des boutons en fonction des positions du json
        for ligne in range(self.BtnH):
            for colonne in range(self.BtnW):
                if (ligne * self.BtnW) + (colonne + 1) in self.positions:
                    for b in self.data_buttons['buttons']:
                        if b['position'] == (ligne * self.BtnW) + (colonne +
                                                                   1):
                            pb = QPushButton(b['name'][:9])
                            pb.setProperty('pbPos', b['position'])
                            pb.setIcon(self.iMinus)
                            # si fond clair, font noire, si sombre, font blanche
                            if (b['r'] * 0.299 + b['g'] * 0.587 +
                                    b['b'] * 0.114) > 186:
                                pb.setStyleSheet(
                                    f"background-color: rgb({b['r']},{b['g']},{b['b']}); color: #000000;"
                                )
                            else:
                                pb.setStyleSheet(
                                    f"background-color: rgb({b['r']},{b['g']},{b['b']}); color: #ffffff;"
                                )
                            self.tableWidget.setCellWidget(ligne, colonne, pb)
                            pb.clicked.connect(self.deleteTw)
                else:
                    pb = QPushButton('Nouveau')
                    calcPos = self.BtnW * ligne + colonne + 1
                    pb.setProperty('pbPos', f"nouveau,{calcPos}")
                    pb.clicked.connect(self.add)
                    self.tableWidget.setCellWidget(ligne, colonne, pb)
                colonne += 1
            ligne += 1

        self.endInitButtons()

    def deleteTw(self):
        pb = self.sender()
        pbPos = pb.property('pbPos')
        for b in self.data_buttons['buttons']:
            if b['position'] == pbPos:
                self.data_buttons['buttons'].remove(b)
                with open('buttons.json', 'w', encoding='utf-8') as outfile:
                    json.dump(self.data_buttons, outfile, indent=4)
                self.delete()

    def editBtn(self):
        self.startInitButtons()

        # positionnement des boutons en fonction des positions du json
        for ligne in range(self.BtnH):
            for colonne in range(self.BtnW):
                if (ligne * self.BtnW) + (colonne + 1) in self.positions:
                    for b in self.data_buttons['buttons']:
                        if b['position'] == (ligne * self.BtnW) + (colonne +
                                                                   1):
                            pb = QPushButton(b['name'][:9])
                            pb.setProperty('pbPos', b['position'])
                            pb.setIcon(self.iEdit)
                            # si fond clair, font noire, si sombre, font blanche
                            if (b['r'] * 0.299 + b['g'] * 0.587 +
                                    b['b'] * 0.114) > 186:
                                pb.setStyleSheet(
                                    f"background-color: rgb({b['r']},{b['g']},{b['b']}); color: #000000;"
                                )
                            else:
                                pb.setStyleSheet(
                                    f"background-color: rgb({b['r']},{b['g']},{b['b']}); color: #ffffff;"
                                )
                            self.tableWidget.setCellWidget(ligne, colonne, pb)
                            pb.clicked.connect(self.editTw)
                else:
                    pb = QPushButton('Nouveau')
                    pb.setIcon(self.iEdit)
                    calcPos = self.BtnW * ligne + colonne + 1
                    pb.setProperty('pbPos', f"nouveau,{calcPos}")
                    pb.clicked.connect(self.add)
                    self.tableWidget.setCellWidget(ligne, colonne, pb)
                colonne += 1
            ligne += 1

        self.endInitButtons()

    def editTw(self):
        pb = self.sender()
        pbPos = pb.property('pbPos')
        self.pbPosToModify = pbPos
        self.add()
        for b in self.data_buttons['buttons']:
            if b['position'] == pbPos:
                self.leName.setText(b['name'])
                self.leFile.setText(b['file'])
                self.lePos.setText(str(b['position']))
                self.cdColorPicker.setCurrentColor(
                    QColor(b['r'], b['g'], b['b']))

    def settings(self):
        self.tableWidget.clear()
        self.tableWidget.clearSpans()
        self.tableWidget.setColumnWidth(2, 100)

        self.cdColorPicker.setVisible(False)

        self.tableWidget.setColumnCount(2)
        self.tableWidget.setRowCount(4)
        self.tableWidget.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.Stretch)

        self.tableWidget.horizontalHeader().hide()

        # bouton validation
        pb = QPushButton('Valider')
        self.tableWidget.setCellWidget(3, 0, pb)
        pb.clicked.connect(self.saveSettings)

        # bouton annulation
        pb = QPushButton('Annuler')
        self.tableWidget.setCellWidget(3, 1, pb)
        pb.clicked.connect(self.refreshUI)

        # parameters
        self.tableWidget.setSpan(0, 0, 1, 2)
        self.lAlert = QLabel("La modification de ces valeurs entrainera la "
                             "modification de position des boutons")
        self.lAlert.setStyleSheet("font-weight: bold;")
        self.tableWidget.setCellWidget(0, 0, self.lAlert)
        self.tableWidget.setCellWidget(1, 0,
                                       QLabel('Nombre de boutons en Hauteur'))
        self.leH = QLineEdit(str(self.data_buttons['buttons_grid']['height']))
        self.tableWidget.setCellWidget(1, 1, self.leH)
        self.tableWidget.setCellWidget(2, 0,
                                       QLabel('Nombre de boutons en Largeur'))
        self.leW = QLineEdit(str(self.data_buttons['buttons_grid']['width']))
        self.tableWidget.setCellWidget(2, 1, self.leW)

        settingsLayout = QVBoxLayout()
        settingsLayout.setStretch(0, 1)
        settingsLayout.addWidget(self.tableWidget)

        self.windowLayout.addLayout(settingsLayout)

        self.setGeometry(self.left, self.top, 600, 300)

    def saveSettings(self):
        h = int(self.leH.text())
        w = int(self.leW.text())
        if h * w < self.max_pos:
            self.lAlert.setText(f"Le bouton à la position {str(self.max_pos)} "
                                f"est en dehors de la grille {h} x {w}")
            self.lAlert.setStyleSheet(
                "color: rgb(255,0,0); font-weight: bold;")
        else:
            self.data_buttons['buttons_grid']['height'] = int(self.leH.text())
            self.data_buttons['buttons_grid']['width'] = int(self.leW.text())
            with open('buttons.json', 'w', encoding='utf-8') as outfile:
                json.dump(self.data_buttons, outfile, indent=4)
            self.initButtons()

    def refreshUI(self):
        self.initButtons()

    def browseMedia(self):
        self.openFile = QFileDialog.getOpenFileName(
            self, "Sélectionner un média...", "./sons",
            "Image Files (*.avi *.mp3 *.wav)")
        filenameSplitted = self.openFile[0].split('/')
        self.leFile.setText(filenameSplitted[-1])

    def colorChanged(self):
        self.colorSelected = self.cdColorPicker.currentColor()
        self.leColor.setText(
            str(self.colorSelected.red()) + "," +
            str(self.colorSelected.green()) + "," +
            str(self.colorSelected.blue()))
Example #5
0
class _HeaderWithButton(QHeaderView):
    """Class that reimplements the QHeaderView section paint event to draw a button
    that is used to display and change the type of that column or row.
    """
    def __init__(self, orientation, parent=None):
        super().__init__(orientation, parent)
        self.setHighlightSections(True)
        self.setSectionsClickable(True)
        self.setDefaultAlignment(Qt.AlignLeft)
        self.sectionResized.connect(self._section_resize)
        self.sectionMoved.connect(self._section_move)
        self._font = QFont('Font Awesome 5 Free Solid')

        self._display_all = True
        self._display_sections = []

        self._margin = Margin(left=0, right=0, top=0, bottom=0)

        self._menu = _create_allowed_types_menu(self, self._menu_pressed)

        self._button = QToolButton(parent=self)
        self._button.setMenu(self._menu)
        self._button.setPopupMode(QToolButton.InstantPopup)
        self._button.setFont(self._font)
        self._button.hide()

        self._render_button = QToolButton(parent=self)
        self._render_button.setFont(self._font)
        self._render_button.hide()

        self._button_logical_index = None
        self.setMinimumSectionSize(self.minimumSectionSize() +
                                   self.widget_width())

    @property
    def display_all(self):
        return self._display_all

    @display_all.setter
    def display_all(self, display_all):
        self._display_all = display_all
        self.viewport().update()

    @property
    def sections_with_buttons(self):
        return self._display_sections

    @sections_with_buttons.setter
    def sections_with_buttons(self, sections):
        self._display_sections = set(sections)
        self.viewport().update()

    @Slot("QAction")
    def _menu_pressed(self, action):
        """Sets the data type of a row or column according to menu action."""
        type_str = action.text()
        self.set_data_types(self._button_logical_index,
                            type_str,
                            update_viewport=False)

    def set_data_types(self, sections, type_str, update_viewport=True):
        """
        Sets the data types of given sections (rows, columns).

        Args:
            sections (Iterable or int or NoneType): row/column index
            type_str (str): data type name
            update_viewport (bool): True if the buttons need repaint
        """
        if type_str == "integer sequence datetime":
            dialog = NewIntegerSequenceDateTimeConvertSpecDialog()
            if dialog.exec_():
                convert_spec = dialog.get_spec()
            else:
                return
        else:
            convert_spec = value_to_convert_spec(type_str)
        if not isinstance(sections, Iterable):
            sections = [sections]
        orientation = self.orientation()
        for section in sections:
            self.model().set_type(section, convert_spec, orientation)
        if update_viewport:
            self.viewport().update()

    def widget_width(self):
        """Width of widget

        Returns:
            [int] -- Width of widget
        """
        if self.orientation() == Qt.Horizontal:
            return self.height()
        return self.sectionSize(0)

    def widget_height(self):
        """Height of widget

        Returns:
            [int] -- Height of widget
        """
        if self.orientation() == Qt.Horizontal:
            return self.height()
        return self.sectionSize(0)

    def mouseMoveEvent(self, mouse_event):
        """Moves the button to the correct section so that interacting with the button works.
        """
        log_index = self.logicalIndexAt(mouse_event.x(), mouse_event.y())
        if not self._display_all and log_index not in self._display_sections:
            self._button_logical_index = None
            self._button.hide()
            super().mouseMoveEvent(mouse_event)
            return

        if self._button_logical_index != log_index:
            self._button_logical_index = log_index
            self._set_button_geometry(self._button, log_index)
            self._button.show()
        super().mouseMoveEvent(mouse_event)

    def mousePressEvent(self, mouse_event):
        """Move the button to the pressed location and show or hide it if button should not be shown.
        """
        log_index = self.logicalIndexAt(mouse_event.x(), mouse_event.y())
        if not self._display_all and log_index not in self._display_sections:
            self._button_logical_index = None
            self._button.hide()
            super().mousePressEvent(mouse_event)
            return

        if self._button_logical_index != log_index:
            self._button_logical_index = log_index
            self._set_button_geometry(self._button, log_index)
            self._button.show()
        super().mousePressEvent(mouse_event)

    def leaveEvent(self, event):
        """Hide button
        """
        self._button_logical_index = None
        self._button.hide()
        super().leaveEvent(event)

    def _set_button_geometry(self, button, index):
        """Sets a buttons geometry depending on the index.

        Arguments:
            button {QWidget} -- QWidget that geometry should be set
            index {int} -- logical_index to set position and geometry to.
        """
        margin = self._margin
        if self.orientation() == Qt.Horizontal:
            button.setGeometry(
                self.sectionViewportPosition(index) + margin.left,
                margin.top,
                self.widget_width() - self._margin.left - self._margin.right,
                self.widget_height() - margin.top - margin.bottom,
            )
        else:
            button.setGeometry(
                margin.left,
                self.sectionViewportPosition(index) + margin.top,
                self.widget_width() - self._margin.left - self._margin.right,
                self.widget_height() - margin.top - margin.bottom,
            )

    def _section_resize(self, i):
        """When a section is resized.

        Arguments:
            i {int} -- logical index to section being resized
        """
        self._button.hide()
        if i == self._button_logical_index:
            self._set_button_geometry(self._button, self._button_logical_index)

    def paintSection(self, painter, rect, logical_index):
        """Paints a section of the QHeader view.

        Works by drawing a pixmap of the button to the left of the original paint rectangle.
        Then shifts the original rect to the right so these two doesn't paint over each other.
        """
        if not self._display_all and logical_index not in self._display_sections:
            super().paintSection(painter, rect, logical_index)
            return

        # get the type of the section.
        type_spec = self.model().get_type(logical_index, self.orientation())
        if type_spec is None:
            type_spec = "string"
        else:
            type_spec = type_spec.DISPLAY_NAME
        font_str = _TYPE_TO_FONT_AWESOME_ICON[type_spec]

        # set data for both interaction button and render button.
        self._button.setText(font_str)
        self._render_button.setText(font_str)
        self._set_button_geometry(self._render_button, logical_index)

        # get pixmap from render button and draw into header section.
        rw = self._render_button.grab()
        if self.orientation() == Qt.Horizontal:
            painter.drawPixmap(self.sectionViewportPosition(logical_index), 0,
                               rw)
        else:
            painter.drawPixmap(0, self.sectionViewportPosition(logical_index),
                               rw)

        # shift rect that super class should paint in to the right so it doesn't
        # paint over the button
        rect.adjust(self.widget_width(), 0, 0, 0)
        super().paintSection(painter, rect, logical_index)

    def sectionSizeFromContents(self, logical_index):
        """Add the button width to the section so it displays right.

        Arguments:
            logical_index {int} -- logical index of section

        Returns:
            [QSize] -- Size of section
        """
        org_size = super().sectionSizeFromContents(logical_index)
        org_size.setWidth(org_size.width() + self.widget_width())
        return org_size

    def _section_move(self, logical, old_visual_index, new_visual_index):
        """Section beeing moved.

        Arguments:
            logical {int} -- logical index of section beeing moved.
            old_visual_index {int} -- old visual index of section
            new_visual_index {int} -- new visual index of section
        """
        self._button.hide()
        if self._button_logical_index is not None:
            self._set_button_geometry(self._button, self._button_logical_index)

    def fix_widget_positions(self):
        """Update position of interaction button
        """
        if self._button_logical_index is not None:
            self._set_button_geometry(self._button, self._button_logical_index)

    def set_margins(self, margins):
        """Sets the header margins."""
        self._margin = margins
class ManageRelationshipsDialog(AddOrManageRelationshipsDialog):
    """A dialog to query user's preferences for managing relationships.
    """
    def __init__(self, parent, db_mngr, *db_maps, relationship_class_key=None):
        """
        Args:
            parent (SpineDBEditor): data store widget
            db_mngr (SpineDBManager): the manager to do the removal
            *db_maps: DiffDatabaseMapping instances
            relationship_class_key (str, optional): relationships class name, object_class name list string.
        """
        super().__init__(parent, db_mngr, *db_maps)
        self.setWindowTitle("Manage relationships")
        self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.remove_rows_button.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self.remove_rows_button.setToolTip(
            "<p>Remove selected relationships.</p>")
        self.remove_rows_button.setIconSize(QSize(24, 24))
        self.db_map = db_maps[0]
        self.relationship_ids = dict()
        layout = self.header_widget.layout()
        self.db_combo_box = QComboBox(self)
        layout.addSpacing(32)
        layout.addWidget(QLabel("Database"))
        layout.addWidget(self.db_combo_box)
        self.splitter = QSplitter(self)
        self.add_button = QToolButton(self)
        self.add_button.setToolTip(
            "<p>Add relationships by combining selected available objects.</p>"
        )
        self.add_button.setIcon(QIcon(":/icons/menu_icons/cubes_plus.svg"))
        self.add_button.setIconSize(QSize(24, 24))
        self.add_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.add_button.setText(">>")
        label_available = QLabel("Available objects")
        label_existing = QLabel("Existing relationships")
        self.layout().addWidget(self.header_widget, 0, 0, 1, 4,
                                Qt.AlignHCenter)
        self.layout().addWidget(label_available, 1, 0)
        self.layout().addWidget(label_existing, 1, 2)
        self.layout().addWidget(self.splitter, 2, 0)
        self.layout().addWidget(self.add_button, 2, 1)
        self.layout().addWidget(self.table_view, 2, 2)
        self.layout().addWidget(self.remove_rows_button, 2, 3)
        self.layout().addWidget(self.button_box, 3, 0, -1, -1)
        self.hidable_widgets = [
            self.add_button,
            label_available,
            label_existing,
            self.table_view,
            self.remove_rows_button,
        ]
        for widget in self.hidable_widgets:
            widget.hide()
        self.existing_items_model = MinimalTableModel(self, lazy=False)
        self.new_items_model = MinimalTableModel(self, lazy=False)
        self.model.sub_models = [
            self.new_items_model, self.existing_items_model
        ]
        self.db_combo_box.addItems([db_map.codename for db_map in db_maps])
        self.reset_relationship_class_combo_box(db_maps[0].codename,
                                                relationship_class_key)
        self.connect_signals()

    def make_model(self):
        return CompoundTableModel(self)

    def splitter_widgets(self):
        return [self.splitter.widget(i) for i in range(self.splitter.count())]

    def connect_signals(self):
        """Connect signals to slots."""
        super().connect_signals()
        self.db_combo_box.currentTextChanged.connect(
            self.reset_relationship_class_combo_box)
        self.add_button.clicked.connect(self.add_relationships)

    @Slot(str)
    def reset_relationship_class_combo_box(self,
                                           database,
                                           relationship_class_key=None):
        self.db_map = self.keyed_db_maps[database]
        self.relationship_class_keys = list(
            self.db_map_rel_cls_lookup[self.db_map])
        self.rel_cls_combo_box.addItems(
            [f"{name}" for name, _ in self.relationship_class_keys])
        try:
            current_index = self.relationship_class_keys.index(
                relationship_class_key)
            self.reset_model(current_index)
            self._handle_model_reset()
        except ValueError:
            current_index = -1
        self.rel_cls_combo_box.setCurrentIndex(current_index)

    @Slot(bool)
    def add_relationships(self, checked=True):
        object_names = [[item.text(0) for item in wg.selectedItems()]
                        for wg in self.splitter_widgets()]
        candidate = list(product(*object_names))
        existing = self.new_items_model._main_data + self.existing_items_model._main_data
        to_add = list(set(candidate) - set(existing))
        count = len(to_add)
        self.new_items_model.insertRows(0, count)
        self.new_items_model._main_data[0:count] = to_add
        self.model.refresh()

    @Slot(int)
    def reset_model(self, index):
        """Setup model according to current relationship_class selected in combobox.
        """
        self.class_name, self.object_class_name_list = self.relationship_class_keys[
            index]
        object_class_name_list = self.object_class_name_list.split(",")
        self.model.set_horizontal_header_labels(object_class_name_list)
        self.existing_items_model.set_horizontal_header_labels(
            object_class_name_list)
        self.new_items_model.set_horizontal_header_labels(
            object_class_name_list)
        self.relationship_ids.clear()
        for db_map in self.db_maps:
            relationship_classes = self.db_map_rel_cls_lookup[db_map]
            rel_cls = relationship_classes.get(
                (self.class_name, self.object_class_name_list), None)
            if rel_cls is None:
                continue
            for relationship in self.db_mngr.get_items_by_field(
                    db_map, "relationship", "class_id", rel_cls["id"]):
                key = tuple(relationship["object_name_list"].split(","))
                self.relationship_ids[key] = relationship["id"]
        existing_items = list(self.relationship_ids)
        self.existing_items_model.reset_model(existing_items)
        self.model.refresh()
        self.model.modelReset.emit()
        for wg in self.splitter_widgets():
            wg.deleteLater()
        for name in object_class_name_list:
            tree_widget = QTreeWidget(self)
            tree_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
            tree_widget.setColumnCount(1)
            tree_widget.setIndentation(0)
            header_item = QTreeWidgetItem([name])
            header_item.setTextAlignment(0, Qt.AlignHCenter)
            tree_widget.setHeaderItem(header_item)
            objects = self.db_mngr.get_items_by_field(self.db_map, "object",
                                                      "class_name", name)
            items = [QTreeWidgetItem([obj["name"]]) for obj in objects]
            tree_widget.addTopLevelItems(items)
            tree_widget.resizeColumnToContents(0)
            self.splitter.addWidget(tree_widget)
        sizes = [wg.columnWidth(0) for wg in self.splitter_widgets()]
        self.splitter.setSizes(sizes)
        for widget in self.hidable_widgets:
            widget.show()

    def resize_window_to_columns(self, height=None):
        table_view_width = (self.table_view.frameWidth() * 2 +
                            self.table_view.verticalHeader().width() +
                            self.table_view.horizontalHeader().length())
        self.table_view.setMinimumWidth(table_view_width)
        self.table_view.setMinimumHeight(
            self.table_view.verticalHeader().defaultSectionSize() * 16)
        margins = self.layout().contentsMargins()
        if height is None:
            height = self.sizeHint().height()
        self.resize(
            margins.left() + margins.right() + table_view_width +
            self.add_button.width() + self.splitter.width(),
            height,
        )

    @Slot()
    def accept(self):
        """Collect info from dialog and try to add items."""
        keys_to_remove = set(self.relationship_ids) - set(
            self.existing_items_model._main_data)
        to_remove = [self.relationship_ids[key] for key in keys_to_remove]
        self.db_mngr.remove_items({self.db_map: {"relationship": to_remove}})
        to_add = [[self.class_name, object_name_list]
                  for object_name_list in self.new_items_model._main_data]
        self.db_mngr.import_data({self.db_map: {
            "relationships": to_add
        }},
                                 command_text="Add relationships")
        super().accept()
 def __init__(self, parent, object_class_item, db_mngr, *db_maps):
     """
     Args:
         parent (SpineDBEditor): data store widget
         object_class_item (ObjectClassItem)
         db_mngr (SpineDBManager)
         *db_maps: database mappings
     """
     super().__init__(parent)
     self.object_class_item = object_class_item
     self.db_mngr = db_mngr
     self.db_maps = db_maps
     self.db_map = db_maps[0]
     self.db_maps_by_codename = {
         db_map.codename: db_map
         for db_map in db_maps
     }
     self.db_combo_box = QComboBox(self)
     self.header_widget = QWidget(self)
     self.group_name_line_edit = QLineEdit(self)
     header_layout = QHBoxLayout(self.header_widget)
     header_layout.addWidget(QLabel(f"Group name: "))
     header_layout.addWidget(self.group_name_line_edit)
     header_layout.addSpacing(32)
     header_layout.addWidget(QLabel("Database"))
     header_layout.addWidget(self.db_combo_box)
     self.non_members_tree = QTreeWidget(self)
     self.non_members_tree.setHeaderLabel("Non members")
     self.non_members_tree.setSelectionMode(QTreeWidget.ExtendedSelection)
     self.non_members_tree.setColumnCount(1)
     self.non_members_tree.setIndentation(0)
     self.members_tree = QTreeWidget(self)
     self.members_tree.setHeaderLabel("Members")
     self.members_tree.setSelectionMode(QTreeWidget.ExtendedSelection)
     self.members_tree.setColumnCount(1)
     self.members_tree.setIndentation(0)
     self.add_button = QToolButton()
     self.add_button.setToolTip("<p>Add selected non-members.</p>")
     self.add_button.setIcon(QIcon(":/icons/menu_icons/cube_plus.svg"))
     self.add_button.setIconSize(QSize(24, 24))
     self.add_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
     self.add_button.setText(">>")
     self.remove_button = QToolButton()
     self.remove_button.setToolTip("<p>Remove selected members.</p>")
     self.remove_button.setIcon(QIcon(":/icons/menu_icons/cube_minus.svg"))
     self.remove_button.setIconSize(QSize(24, 24))
     self.remove_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
     self.remove_button.setText("<<")
     self.vertical_button_widget = QWidget()
     vertical_button_layout = QVBoxLayout(self.vertical_button_widget)
     vertical_button_layout.addStretch()
     vertical_button_layout.addWidget(self.add_button)
     vertical_button_layout.addWidget(self.remove_button)
     vertical_button_layout.addStretch()
     self.button_box = QDialogButtonBox(self)
     self.button_box.setStandardButtons(QDialogButtonBox.Cancel
                                        | QDialogButtonBox.Ok)
     layout = QGridLayout(self)
     layout.addWidget(self.header_widget, 0, 0, 1, 3, Qt.AlignHCenter)
     layout.addWidget(self.non_members_tree, 1, 0)
     layout.addWidget(self.vertical_button_widget, 1, 1)
     layout.addWidget(self.members_tree, 1, 2)
     layout.addWidget(self.button_box, 2, 0, 1, 3)
     self.setAttribute(Qt.WA_DeleteOnClose)
     self.db_combo_box.addItems(list(self.db_maps_by_codename))
     self.db_map_object_ids = {
         db_map: {
             x["name"]: x["id"]
             for x in self.db_mngr.get_items_by_field(
                 self.db_map, "object", "class_id",
                 self.object_class_item.db_map_id(db_map))
         }
         for db_map in db_maps
     }
     self.reset_list_widgets(db_maps[0].codename)
     self.connect_signals()
Example #8
0
    def Button(self):
        Styles = [
            QStyle.SP_TitleBarMinButton,
            QStyle.SP_TitleBarMenuButton,
            QStyle.SP_TitleBarMaxButton,
            QStyle.SP_TitleBarCloseButton,
            QStyle.SP_TitleBarNormalButton,
            QStyle.SP_TitleBarShadeButton,
            QStyle.SP_TitleBarUnshadeButton,
            QStyle.SP_TitleBarContextHelpButton,
            QStyle.SP_MessageBoxInformation,
            QStyle.SP_MessageBoxWarning,
            QStyle.SP_MessageBoxCritical,
            QStyle.SP_MessageBoxQuestion,
            QStyle.SP_DesktopIcon,
            QStyle.SP_TrashIcon,
            QStyle.SP_ComputerIcon,
            QStyle.SP_DriveFDIcon,
            QStyle.SP_DriveHDIcon,
            QStyle.SP_DriveCDIcon,
            QStyle.SP_DriveDVDIcon,
            QStyle.SP_DriveNetIcon,
            QStyle.SP_DirHomeIcon,
            QStyle.SP_DirOpenIcon,
            QStyle.SP_DirClosedIcon,
            QStyle.SP_DirIcon,
            QStyle.SP_DirLinkIcon,
            QStyle.SP_DirLinkOpenIcon,
            QStyle.SP_FileIcon,
            QStyle.SP_FileLinkIcon,
            QStyle.SP_FileDialogStart,
            QStyle.SP_FileDialogEnd,
            QStyle.SP_FileDialogToParent,
            QStyle.SP_FileDialogNewFolder,
            QStyle.SP_FileDialogDetailedView,
            QStyle.SP_FileDialogInfoView,
            QStyle.SP_FileDialogContentsView,
            QStyle.SP_FileDialogListView,
            QStyle.SP_FileDialogBack,
            QStyle.SP_DockWidgetCloseButton,
            QStyle.SP_ToolBarHorizontalExtensionButton,
            QStyle.SP_ToolBarVerticalExtensionButton,
            QStyle.SP_DialogOkButton,
            QStyle.SP_DialogCancelButton,
            QStyle.SP_DialogHelpButton,
            QStyle.SP_DialogOpenButton,
            QStyle.SP_DialogSaveButton,
            QStyle.SP_DialogCloseButton,
            QStyle.SP_DialogApplyButton,
            QStyle.SP_DialogResetButton,
            QStyle.SP_DialogDiscardButton,
            QStyle.SP_DialogYesButton,
            QStyle.SP_DialogNoButton,
            QStyle.SP_ArrowUp,
            QStyle.SP_ArrowDown,
            QStyle.SP_ArrowLeft,
            QStyle.SP_ArrowRight,
            QStyle.SP_ArrowBack,
            QStyle.SP_ArrowForward,
            QStyle.SP_CommandLink,
            QStyle.SP_VistaShield,
            QStyle.SP_BrowserReload,
            QStyle.SP_BrowserStop,
            QStyle.SP_MediaPlay,
            QStyle.SP_MediaStop,
            QStyle.SP_MediaPause,
            QStyle.SP_MediaSkipForward,
            QStyle.SP_MediaSkipBackward,
            QStyle.SP_MediaSeekForward,
            QStyle.SP_MediaSeekBackward,
            QStyle.SP_MediaVolume,
            QStyle.SP_MediaVolumeMuted,
            QStyle.SP_LineEditClearButton,
            #QStyle.SP_DialogYesToAllButton,
            #QStyle.SP_DialogNoToAllButton,
            #QStyle.SP_DialogSaveAllButton,
            #QStyle.SP_DialogAbortButton,
            #QStyle.SP_DialogRetryButton,
            #QStyle.SP_DialogIgnoreButton,
            #QStyle.SP_RestoreDefaultsButton,
            #QStyle.SP_CustomBase,
        ]

        StylesText = [
            'SP_TitleBarMinButton',
            'SP_TitleBarMenuButton',
            'SP_TitleBarMaxButton',
            'SP_TitleBarCloseButton',
            'SP_TitleBarNormalButton',
            'SP_TitleBarShadeButton',
            'SP_TitleBarUnshadeButton',
            'SP_TitleBarContextHelpButton',
            'SP_MessageBoxInformation',
            'SP_MessageBoxWarning',
            'SP_MessageBoxCritical',
            'SP_MessageBoxQuestion',
            'SP_DesktopIcon',
            'SP_TrashIcon',
            'SP_ComputerIcon',
            'SP_DriveFDIcon',
            'SP_DriveHDIcon',
            'SP_DriveCDIcon',
            'SP_DriveDVDIcon',
            'SP_DriveNetIcon',
            'SP_DirHomeIcon',
            'SP_DirOpenIcon',
            'SP_DirClosedIcon',
            'SP_DirIcon',
            'SP_DirLinkIcon',
            'SP_DirLinkOpenIcon',
            'SP_FileIcon',
            'SP_FileLinkIcon',
            'SP_FileDialogStart',
            'SP_FileDialogEnd',
            'SP_FileDialogToParent',
            'SP_FileDialogNewFolder',
            'SP_FileDialogDetailedView',
            'SP_FileDialogInfoView',
            'SP_FileDialogContentsView',
            'SP_FileDialogListView',
            'SP_FileDialogBack',
            'SP_DockWidgetCloseButton',
            'SP_ToolBarHorizontalExtensionButton',
            'SP_ToolBarVerticalExtensionButton',
            'SP_DialogOkButton',
            'SP_DialogCancelButton',
            'SP_DialogHelpButton',
            'SP_DialogOpenButton',
            'SP_DialogSaveButton',
            'SP_DialogCloseButton',
            'SP_DialogApplyButton',
            'SP_DialogResetButton',
            'SP_DialogDiscardButton',
            'SP_DialogYesButton',
            'SP_DialogNoButton',
            'SP_ArrowUp',
            'SP_ArrowDown',
            'SP_ArrowLeft',
            'SP_ArrowRight',
            'SP_ArrowBack',
            'SP_ArrowForward',
            'SP_CommandLink',
            'SP_VistaShield',
            'SP_BrowserReload',
            'SP_BrowserStop',
            'SP_MediaPlay',
            'SP_MediaStop',
            'SP_MediaPause',
            'SP_MediaSkipForward',
            'SP_MediaSkipBackward',
            'SP_MediaSeekForward',
            'SP_MediaSeekBackward',
            'SP_MediaVolume',
            'SP_MediaVolumeMuted',
            'SP_LineEditClearButton',
            #'SP_DialogYesToAllButton',
            #'SP_DialogNoToAllButton',
            #'SP_DialogSaveAllButton',
            #'SP_DialogAbortButton',
            #'SP_DialogRetryButton',
            #'SP_DialogIgnoreButton',
            #'SP_RestoreDefaultsButton',
            #'SP_CustomBase',
        ]

        btn = [QToolButton(self) for i in range(len(Styles))]

        self.myHLayout = QGridLayout()

        j = 0
        k = 0
        style = self.style()
        for i in range(len(Styles)):
            btn[i].setText("%s" % (StylesText[i]))
            btn[i].setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
            icon = style.standardIcon(Styles[i])
            btn[i].setIcon(QIcon(icon))

            self.myHLayout.addWidget(btn[i], j, k)

            if i == 0:
                pass
            elif 0 == i % 5:
                j += 1
                k = 0
            else:
                k += 1

        self.setLayout(self.myHLayout)
Example #9
0
class camera_view(QWidget):
    """docstring for camera_view."""

    def __init__(self, camera):
        super(camera_view, self).__init__()
        self.camera = camera
        self.image_camera = np.zeros((1,1))
        #self.image = QImage()

        self.button_up = QToolButton()
        self.button_up.setArrowType(Qt.UpArrow)
        self.button_down = QToolButton()
        self.button_down.setArrowType(Qt.DownArrow)
        self.button_left = QToolButton()
        self.button_left.setArrowType(Qt.LeftArrow)
        self.button_right = QToolButton()
        self.button_right.setArrowType(Qt.RightArrow)

        self.box_button = QVBoxLayout()
        self.box_button_centre = QHBoxLayout()
        self.box_button_centre.addWidget(self.button_left)
        self.box_button_centre.addWidget(self.button_right)
        self.box_button_centre.addStretch(1)
        self.box_button.addWidget(self.button_up)
        self.box_button.addLayout(self.box_button_centre)
        self.box_button.addWidget(self.button_down)
        self.box_button.addStretch(1)

        self.button_affichage = QPushButton("Affichage Vidéo")
        self.button_affichage.clicked.connect(self.Affichage)

        self.button_stop = QPushButton("Stop retour Vidéo")
        self.button_stop.clicked.connect(self.fin_video)
        self.box = QVBoxLayout()
        self.box.addWidget(self.button_affichage)
        self.box.addWidget(self.button_stop)

        self.box_panel = QHBoxLayout()
        self.box_panel.addLayout(self.box)
        self.box_panel.addLayout(self.box_button)

        self.image2 = QLabel()
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.image2)

        self.layout.addLayout(self.box_panel)
        self.setLayout(self.layout)


        self.movie_thread = MovieThread(self.camera)
        self.movie_thread.changePixmap.connect(self.setImage)
        self.movie_thread.setTerminationEnabled(True)

        #
        self.button_up.clicked.connect(self.up)
        self.button_down.clicked.connect(self.down)
        self.button_right.clicked.connect(self.right)
        self.button_left.clicked.connect(self.left)

        #box enregistrement + photo
        self.button_debut_video = QPushButton("Début enregistrement Vidéo")
        self.button_fin_video = QPushButton("Fin enregistrement Vidéo")
        self.button_photo = QPushButton("Photo")
        self.box_video_photo = QVBoxLayout()
        self.box_video_photo.addWidget(self.button_debut_video)
        self.box_video_photo.addWidget(self.button_fin_video)
        self.box_video_photo.addWidget(self.button_photo)
        self.box_panel.addLayout(self.box_video_photo)

        self.enregistrement = Record(self.camera)
        self.enregistrement.setTerminationEnabled(True)

        self.button_debut_video.clicked.connect(self.video_debut)
        self.button_fin_video.clicked.connect(self.video_fin)
        self.button_photo.clicked.connect(self.photo)
        self.compteur_photo = 0


        self.enregistrement.start()
        self.timer_enregistrement = QTimer()
        self.timer_enregistrement.setInterval((1/24)*1000)
        self.timer_enregistrement.timeout.connect(self.video)
        #self.Timer.setInterval(500)


    def video(self):
        self.enregistrement.video()
        pass





    def photo(self):
        cv2.imwrite("./photo"+str(self.compteur_photo)+".png",self.camera.last_frame[1])
        self.compteur_photo += 1


        pass

    def video_debut(self):
        self.timer_enregistrement.start()

        pass


    def video_fin(self):
        print("nombre photo ",self.enregistrement.nombre)
        self.enregistrement.nombre = 0
        self.timer_enregistrement.stop()
        self.enregistrement.stop()

        pass



    def up(self):
        self.client.envoie('up')
        print('up')
        pass

    def down(self):
        self.client.envoie('down')
        print('down')

        pass

    def right(self):
        self.client.envoie('right')
        print('right')
        pass

    def left(self):
        self.client.envoie('left')
        print('left')
        pass

    def Affichage(self):
        self.client = Client("192.168.1.62",35351)

        self.movie_thread.start()

    def fin_video(self):
        self.movie_thread.stop()
        self.client.envoie('q')

    def setImage(self,image):
        self.image2.setPixmap(QPixmap.fromImage(image))
Example #10
0
 def add_execute_buttons(self):
     icon_size = 24
     self.addSeparator()
     self.addWidget(QLabel("Execution"))
     execute_project_icon = QIcon(
         ":/icons/project_item_icons/play-circle-solid.svg").pixmap(
             icon_size, icon_size)
     execute_project = QToolButton(self)
     execute_project.setIcon(execute_project_icon)
     execute_project.clicked.connect(self.execute_project)
     execute_project.setToolTip("Execute project")
     self.addWidget(execute_project)
     execute_selected_icon = QIcon(
         ":/icons/project_item_icons/play-circle-regular.svg").pixmap(
             icon_size, icon_size)
     execute_selected = QToolButton(self)
     execute_selected.setIcon(execute_selected_icon)
     execute_selected.clicked.connect(self.execute_selected)
     execute_selected.setToolTip("Execute selection")
     self.addWidget(execute_selected)
     stop_icon = QIcon(
         ":/icons/project_item_icons/stop-circle-regular.svg").pixmap(
             icon_size, icon_size)
     stop = QToolButton(self)
     stop.setIcon(stop_icon)
     stop.clicked.connect(self.stop_execution)
     stop.setToolTip("Stop execution")
     self.addWidget(stop)
class MyDockWidget(cutter.CutterDockWidget):
    def __init__(self, parent, action):
        super(MyDockWidget, self).__init__(parent, action)
        self.setObjectName("Capa explorer")
        self.setWindowTitle("Capa explorer")

        self._config = CutterBindings.Configuration.instance()
        self.model_data = CapaExplorerDataModel()

        self.range_model_proxy = CapaExplorerRangeProxyModel()
        self.range_model_proxy.setSourceModel(self.model_data)
        self.search_model_proxy = CapaExplorerSearchProxyModel()
        self.search_model_proxy.setSourceModel(self.range_model_proxy)

        self.create_view_tabs()
        self.create_menu()
        self.create_tree_tab_ui()
        self.create_view_attack()

        self.connect_signals()
        self.setWidget(self.tabs)
        self.show()

    def create_view_tabs(self):

        # Create tabs container
        self.tabs = QTabWidget()

        # Create the tabs
        self.tab_attack = QWidget(self.tabs)
        self.tab_tree_w_model = QWidget(self.tabs)

        self.tabs.addTab(self.tab_tree_w_model, "Tree View")
        self.tabs.addTab(self.tab_attack, "MITRE")

    def create_menu(self):
        # Define menu actions
        # Text, tooltip, function, enabled before file load
        self.disabled_menu_items = []

        menu_actions = [
            ("Load JSON file", '', self.cma_load_file, True),
            (),
            ("Auto rename functions",
             'Auto renames functions according to capa detections, can result in very long function names.',
             self.cma_analyze_and_rename, False),
            ("Create flags",
             'Creates flagspaces and flags from capa detections.',
             self.cma_create_flags, False),
            (),
            ("About", '', self.cma_display_about, True),
        ]

        self.capa_menu = QMenu()
        self.capa_menu.setToolTipsVisible(True)

        # Create qactions
        for action in menu_actions:
            if not len(action):
                # Create separator on empty
                self.capa_menu.addSeparator()
                continue

            a = QAction(self)
            a.setText(action[0])
            a.setToolTip(action[1])
            a.triggered.connect(action[2])
            a.setEnabled(action[3])
            if not action[3]:
                self.disabled_menu_items.append(a)
            self.capa_menu.addAction(a)

            # Create menu button
            font = QFont()
            font.setBold(True)
            self.btn_menu = QToolButton()
            self.btn_menu.setText('...')
            self.btn_menu.setFont(font)
            self.btn_menu.setPopupMode(QToolButton.InstantPopup)
            self.btn_menu.setMenu(self.capa_menu)
            self.btn_menu.setStyleSheet(
                'QToolButton::menu-indicator { image: none; }')
            self.tabs.setCornerWidget(self.btn_menu, corner=Qt.TopRightCorner)

    def create_tree_tab_ui(self):
        self.capa_tree_view_layout = QVBoxLayout()
        self.capa_tree_view_layout.setAlignment(Qt.AlignTop)

        self.chk_fcn_scope = QCheckBox("Limit to Current function")
        #TODO: reset state on load file
        self.chk_fcn_scope.setChecked(False)
        self.chk_fcn_scope.stateChanged.connect(
            self.slot_checkbox_limit_by_changed)

        self.input_search = QLineEdit()
        self.input_search.setStyleSheet("margin:0px; padding:0px;")
        self.input_search.setPlaceholderText("search...")
        self.input_search.textChanged.connect(
            self.slot_limit_results_to_search)

        self.filter_controls_container = QGroupBox()
        self.filter_controls_container.setObjectName("scope")
        self.filter_controls_container.setFlat(True)
        self.filter_controls_container.setStyleSheet(
            "#scope{border:0px; padding:0px; margin:0px;subcontrol-origin: padding; subcontrol-position: left top;}"
        )
        self.filter_controls_layout = QHBoxLayout(
            self.filter_controls_container)
        self.filter_controls_layout.setContentsMargins(0, 0, 0, 0)
        self.filter_controls_layout.addWidget(self.input_search)
        self.filter_controls_layout.addWidget(self.chk_fcn_scope)

        self.view_tree = CapaExplorerQtreeView(self.search_model_proxy)
        self.view_tree.setModel(self.search_model_proxy)

        # Make it look a little nicer when no results are loaded
        self.view_tree.header().setStretchLastSection(True)

        self.capa_tree_view_layout.addWidget(self.filter_controls_container)
        self.capa_tree_view_layout.addWidget(self.view_tree)
        self.tab_tree_w_model.setLayout(self.capa_tree_view_layout)

    def create_view_attack(self):
        table_headers = [
            "ATT&CK Tactic",
            "ATT&CK Technique ",
        ]
        table = QTableWidget()
        table.setColumnCount(len(table_headers))
        table.verticalHeader().setVisible(False)
        table.setSortingEnabled(False)
        table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        table.setFocusPolicy(Qt.NoFocus)
        table.setSelectionMode(QAbstractItemView.NoSelection)
        table.setHorizontalHeaderLabels(table_headers)
        table.horizontalHeader().setDefaultAlignment(Qt.AlignLeft)
        table.horizontalHeader().setStretchLastSection(True)
        table.setShowGrid(False)
        table.horizontalHeader().setSectionResizeMode(
            0, QHeaderView.ResizeToContents)
        table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        #table.setStyleSheet("QTableWidget::item { padding: 25px; }")

        attack_view_layout = QVBoxLayout()
        attack_view_layout.setAlignment(Qt.AlignTop)

        self.attack_table = table

        attack_view_layout.addWidget(self.attack_table)
        self.tab_attack.setLayout(attack_view_layout)

        return table

    def connect_signals(self):

        QObject.connect(cutter.core(), SIGNAL("functionRenamed(RVA, QString)"),
                        self.model_data.refresh_function_names)
        QObject.connect(cutter.core(), SIGNAL("functionsChanged()"),
                        self.model_data.refresh_function_names)
        QObject.connect(cutter.core(), SIGNAL("seekChanged(RVA)"),
                        self.signal_shim_slot_checkbox_limit_by_changed)

    def render_new_table_header_item(self, text):
        """create new table header item with our style
        @param text: header text to display
        """
        item = QTableWidgetItem(text)
        item.setForeground(self._config.getColor("graph.true"))
        font = QFont()
        font.setBold(True)
        item.setFont(font)
        return item

    def fill_attack_table(self, rules):
        tactics = collections.defaultdict(set)
        for key, rule in rules.items():
            if not rule["meta"].get("att&ck"):
                continue

            for attack in rule["meta"]["att&ck"]:
                tactic, _, rest = attack.partition("::")
            if "::" in rest:
                technique, _, rest = rest.partition("::")
                subtechnique, _, id = rest.rpartition(" ")
                tactics[tactic].add((technique, subtechnique, id))
            else:
                technique, _, id = rest.rpartition(" ")
                tactics[tactic].add((technique, id))

        column_one = []
        column_two = []

        for (tactic, techniques) in sorted(tactics.items()):
            column_one.append(tactic.upper())
            # add extra space when more than one technique
            column_one.extend(["" for i in range(len(techniques) - 1)])

            for spec in sorted(techniques):
                if len(spec) == 2:
                    technique, id = spec
                    column_two.append("%s %s" % (technique, id))
                elif len(spec) == 3:
                    technique, subtechnique, id = spec
                    column_two.append("%s::%s %s" %
                                      (technique, subtechnique, id))
                else:
                    raise RuntimeError("unexpected ATT&CK spec format")

        self.attack_table.setRowCount(max(len(column_one), len(column_two)))

        for (row, value) in enumerate(column_one):
            self.attack_table.setItem(row, 0,
                                      self.render_new_table_header_item(value))

        for (row, value) in enumerate(column_two):
            self.attack_table.setItem(row, 1, QTableWidgetItem(value))

    def enable_menu_items_after_load(self):
        # enables menu actions after file is loaded
        for action in self.disabled_menu_items:
            action.setEnabled(True)

    def slot_limit_results_to_search(self, text):
        """limit tree view results to search matches
        reset view after filter to maintain level 1 expansion
        """
        self.search_model_proxy.set_query(text)
        self.view_tree.reset_ui(should_sort=False)

    def signal_shim_slot_checkbox_limit_by_changed(self):
        if self.chk_fcn_scope.isChecked():
            self.slot_checkbox_limit_by_changed(Qt.Checked)

    def slot_checkbox_limit_by_changed(self, state):
        """slot activated if checkbox clicked
        if checked, configure function filter if screen location is located in function, otherwise clear filter
        @param state: checked state
        """
        invoke_reset = True

        if state == Qt.Checked:
            minbound, maxbound = util.get_function_boundries_at_current_location(
            )

            if self.range_model_proxy.min_ea == minbound and self.range_model_proxy.max_ea == maxbound:
                # Seek only changed within current function, avoid resetting tree
                invoke_reset = False

            self.limit_results_to_function((minbound, maxbound))
        else:
            self.range_model_proxy.reset_address_range_filter()

        if invoke_reset:
            self.view_tree.reset_ui()

    def limit_results_to_function(self, f):
        """add filter to limit results to current function
        adds new address range filter to include function bounds, allowing basic blocks matched within a function
        to be included in the results
        @param f: (tuple (maxbound, minbound))
        """
        if f:
            self.range_model_proxy.add_address_range_filter(f[0], f[1])
        else:
            # if function not exists don't display any results (assume address never -1)
            self.range_model_proxy.add_address_range_filter(-1, -1)

    # --- Menu Actions

    def cma_analyze_and_rename(self):
        message_box = QMessageBox()

        message_box.setStyleSheet("QLabel{min-width: 370px;}")
        message_box.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok)
        message_box.setEscapeButton(QMessageBox.Cancel)
        message_box.setDefaultButton(QMessageBox.Ok)

        message_box.setWindowTitle('Warning')
        message_box.setText(
            'Depending on the size of the binary and the'
            ' amount of \n'
            'capa matches this feature can take some time to \n'
            'complete and might make the UI freeze temporarily.')
        message_box.setInformativeText('Are you sure you want to proceed ?')

        ret = message_box.exec_()

        # Ok = 1024
        if ret == 1024:
            self.model_data.auto_rename_functions()

    def cma_create_flags(self):
        self.model_data.create_flags()

    def cma_display_about(self):
        c = CAPAExplorerPlugin()

        info_text = ("{description}\n\n"
                     "https://github.com/ninewayhandshake/capa-explorer\n\n"
                     "Version: {version}\n"
                     "Author: {author}\n"
                     "License: Apache License 2.0\n").format(
                         version=c.version,
                         author=c.author,
                         description=c.description,
                     )

        text = CAPAExplorerPlugin().name
        message_box = QMessageBox()
        message_box.setStyleSheet("QLabel{min-width: 370px;}")

        message_box.setWindowTitle('About')
        message_box.setText(text)
        message_box.setInformativeText(info_text)
        message_box.setStandardButtons(QMessageBox.Close)

        for i in message_box.findChildren(QLabel):
            i.setFocusPolicy(Qt.NoFocus)

        message_box.exec_()

    def cma_load_file(self):

        filename = QFileDialog.getOpenFileName()
        path = filename[0]

        if len(path):
            try:
                data = util.load_capa_json(path)

                self.fill_attack_table(data['rules'])
                self.model_data.clear()
                self.model_data.render_capa_doc(data)

                # Restore ability to scroll on last column
                self.view_tree.header().setStretchLastSection(False)

                self.view_tree.slot_resize_columns_to_content()
                self.enable_menu_items_after_load()
            except Exception as e:
                util.log('Could not load json file.')
        else:
            util.log('No file selected.')
Example #12
0
class CadastroPadrao(QWidget):
    """
    Classe padrão para cadastros
    Alguns métodos devem ser reimplementados chamando super(ClasseFilha, self).metodo()

    Métodos:

    void cadastrar()
    void editar()
    void excluir()
    int localizar()
    void limpar_dados()
    void receber_dados(dict())
    void cancela()
    str', 'int valida_obrigatorios()
    bool confirma()
    bool fechar
    bool nao_esta_em_modo_edicao()
    bool esta_em_modo_edicao()
    void entrar_modo_edicao()
    void sair_modo_edicao()
    void define_permite_editar()

    """
    def __init__(self, parent=None, **kwargs):
        super(CadastroPadrao, self).__init__(parent)

        self.dados_formatados = None
        self.db = None
        self.window_list = None

        self.dialog = kwargs.get('dialog')

        if self.dialog:
            self.setWindowFlags(Qt.Dialog)
        else:
            self.setWindowFlags(Qt.Window)

        # Configuracoes
        self.localizar_campos = None
        self.dados = None
        self.modo_edicao = False
        self.novo_cadastro = True
        self.view_busca = None
        self.colunas_busca = None
        self.filtro_adicional = None
        self.campos_obrigatorios = dict()

        # QFrame
        self.frame_menu = None
        self.frame_buttons = None

        self.lineEdit_id = None
        self.pushButton_cadastrar = None
        self.pushButton_editar = None
        self.pushButton_excluir = None
        self.pushButton_localizar = None

        # QWidget
        self.frame_contents = None
        self.parent_window = None

        self.icone_insert = None
        self.icone_update = None
        self.icone_delete = None
        self.icone_find = None
        self.ultimo_id = ''

        #self.define_icones()

    def define_icones(self):
        self.icone_insert = QIcon(
            os.path.join('Resources', 'icons', 'insert.png'))
        self.icone_update = QIcon(
            os.path.join('Resources', 'icons', 'update.png'))
        self.icone_delete = QIcon(
            os.path.join('Resources', 'icons', 'delete.png'))
        self.icone_find = QIcon(os.path.join('Resources', 'icons', 'find.png'))

        if self.pushButton_cadastrar is not None:
            self.pushButton_cadastrar.setIcon(self.icone_insert)

        if self.pushButton_editar is not None:
            self.pushButton_editar.setIcon(self.icone_update)

        if self.pushButton_excluir is not None:
            self.pushButton_excluir.setIcon(self.icone_delete)

        if self.pushButton_localizar is not None:
            self.pushButton_localizar.setIcon(self.icone_find)

        self.translate_ui()

    def translate_ui(self):
        self.buttonBox.button(QDialogButtonBox.Ok).setText('Salvar')
        self.buttonBox.button(QDialogButtonBox.Cancel).setText('Cancelar')

    def adiciona_help(self, texto="Teste ToolButton", layout=None):
        from PySide2.QtWidgets import QToolButton
        self.toolButton_help = QToolButton(self.frame_buttons)
        self.toolButton_help.setToolTipDuration(5000)
        self.toolButton_help.setText("?")
        self.toolButton_help.setToolTip(
            texto + '\n\nCampos destacados em vermelho são obrigatórios.')
        self.toolButton_help.setStyleSheet("background: rgb(38, 183, 212);\n"
                                           "color: white;\n"
                                           "border: 10px;\n"
                                           "border-radius: 5px;")
        self.toolButton_help.setObjectName("toolButton_help")
        self.horizontalLayout_3.addWidget(self.toolButton_help)

    # Reimplementar chamando super
    def cadastrar(self):
        self.entrar_modo_edicao()
        self.novo_cadastro = True
        self.lineEdit_id.setText('')

    # Reimplementar chamando super
    def editar(self):
        self.entrar_modo_edicao()
        self.novo_cadastro = False

    # Reimplementar chamando super
    def excluir(self, validar=True):
        if self.nao_esta_em_modo_edicao():

            if validar:
                dialog = ConfirmDialog(parent=self)
                dialog.definir_mensagem(
                    "Tem certeza que deseja realizar a exclusão desse registro?"
                )
                cancelar = dialog.exec()
            else:
                cancelar = True

            return self.db.call_procedure(self.db.schema,
                                          self.dados) if cancelar else False
        else:
            return False

    def localizar(self, parent=None):

        localizar = LocalizarDialog(db=self.db,
                                    campos=self.localizar_campos,
                                    tabela=self.view_busca,
                                    colunas=self.colunas_busca,
                                    filtro=self.filtro_adicional,
                                    parent=parent)

        localizar.retorno_dados.connect(self.receber_dados)
        modal = localizar.exec()

        return modal if modal > 0 else None

    # Reimplementar chamando super
    def limpar_dados(self):
        # limpa todos os campos
        self.ultimo_id = self.lineEdit_id.text()
        self.lineEdit_id.setText('')

    def receber_dados(self, dados):
        self.dados = dados

    # Reimplementar chamando super e limpar_dados
    def cancela(self):
        if self.modo_edicao:
            dialog = ConfirmDialog(parent=self)
            dialog.definir_mensagem(
                "Tem certeza que deseja cancelar? Todas as alterações serão perdidas."
            )
            cancelar = dialog.exec()

            if cancelar:
                self.sair_modo_edicao()
                return True

        return False

    # Reimplementar chamando super
    def valida_obrigatorios(self):
        if len(self.campos_obrigatorios) > 0:
            vermelho = "247, 192, 188"
            style = "border: 0.5px solid red; background: rgb({});".format(
                vermelho)
            print(style)
            for campo, valor in self.campos_obrigatorios.items():
                valor.setStyleSheet("QLineEdit { background: white; }")
                try:
                    if valor.text() == '':
                        valor.setStyleSheet(style)
                        dialog = StatusDialog(status='ALERTA',
                                              mensagem='O campo ' + campo +
                                              ' é obrigatório.',
                                              parent=self.parent_window)
                        #return dialog.exec()
                        return False
                except AttributeError as attr:
                    if valor.currentText() == '':
                        valor.setStyleSheet(style)
                        dialog = StatusDialog(status='ALERTA',
                                              mensagem='O campo ' + campo +
                                              ' é obrigatório.',
                                              parent=self.parent_window)
                        #return dialog.exec()
                        return False
                except Exception as e:
                    dialog = StatusDialog(
                        status='ERRO',
                        mensagem='Erro ao verificar campos obrigatórios.',
                        exception=e,
                        parent=self.parent_window)
                    return dialog.exec()
        self.marca_obrigatorios()
        return 'OK'

    def marca_obrigatorios(self):
        if len(self.campos_obrigatorios) > 0:
            for campo, valor in self.campos_obrigatorios.items():
                valor.setStyleSheet("\nborder: 0.5px solid red")

    def limpa_obrigatorios(self):
        if len(self.campos_obrigatorios) > 0:
            for campo, valor in self.campos_obrigatorios.items():
                valor.setStyleSheet("border: 0px solid black")
                valor.setStyleSheet("QLineEdit { background: white; }")

    # Reimplementar chamando super
    def confirma(self):
        if self.esta_em_modo_edicao():

            if self.valida_obrigatorios() != 'OK':
                return False

            # pega os dados tela e envia pro banco
            prc = self.db.call_procedure(self.db.schema, self.dados)

            if prc[0]:

                if self.novo_cadastro:
                    acao = 'realizado'
                else:
                    acao = 'atualizado'

                dialog = StatusDialog(status='OK',
                                      mensagem='Cadastro ' + acao +
                                      ' com sucesso!',
                                      parent=self.parent_window)
                self.sair_modo_edicao()

            else:
                dialog = StatusDialog(
                    status='ALERTA',
                    mensagem='Não foi possível salvar os dados.',
                    exception=prc,
                    parent=self.parent_window)
                self.modo_edicao = True

            dialog.exec()
            return prc[0], prc[1][0]

    # Não deve ser reimplementado na tela
    #verifica se tem alguma alteracao pendente e pergunta se deseja fechar
    def fechar(self):
        if self.modo_edicao:
            dialog = ConfirmDialog(parent=self)
            dialog.definir_mensagem(
                "Tem certeza que deseja fechar? Todas as alterações serão perdidas."
            )
            fechar = dialog.exec()
        else:
            fechar = True

        return fechar

    def nao_esta_em_modo_edicao(self):
        if self.modo_edicao:
            dialog = StatusDialog(
                status='ERRO',
                mensagem='A interface está em modo de edição!',
                parent=self.parent_window)
            return dialog.exec()
        else:
            logging.info('[CadastroPadrao] Não está em modo de edição.')
            return True

    def esta_em_modo_edicao(self):
        if not self.modo_edicao:
            dialog = StatusDialog(
                status='ERRO',
                mensagem='A interface não está em modo de edição!',
                parent=self.parent_window)
            return dialog.exec()
        else:
            logging.info('[CadastroPadrao] Está em modo de edição.')
            return True

    def entrar_modo_edicao(self):
        if self.nao_esta_em_modo_edicao():
            self.modo_edicao = True
            self.frame_menu.setDisabled(True)
            self.frame_buttons.setDisabled(False)
            self.frame_contents.setDisabled(False)
            self.marca_obrigatorios()
            logging.info('[CadastroPadrao] Entrando em modo edição')

    def sair_modo_edicao(self):
        if self.esta_em_modo_edicao():
            self.modo_edicao = False
            self.frame_menu.setDisabled(False)
            self.frame_buttons.setDisabled(True)
            self.frame_contents.setDisabled(True)
            self.limpa_obrigatorios()
            self.define_permite_editar()
            logging.info('[CadastroPadrao] Saindo do modo edição')

    def entrar_modo_visualizacao(self):
        if self.nao_esta_em_modo_edicao():
            self.modo_edicao = False
            self.frame_menu.setDisabled(False)
            self.frame_buttons.setDisabled(True)
            self.frame_contents.setDisabled(False)

    def define_permite_editar(self):
        logging.info('[CadastroPadrao] Editar: ' +
                     str(self.lineEdit_id.text() != ''))
        self.pushButton_editar.setDisabled(self.lineEdit_id.text() == '')
        self.pushButton_excluir.setDisabled(self.lineEdit_id.text() == '')

    def formatar_numero(self, numero):
        try:
            numero = str(numero)
            if len(numero.split(',')) > 1:
                numero = '%.2f' % float(numero.replace(',', '.'))
            elif len(numero.split('.')) > 1:
                numero = '%.2f' % float(numero)
                numero = numero.replace('.', ',')
            return str(numero)
        except ValueError:
            return numero

    # Override PySide2.QtGui.QCloseEvent
    def closeEvent(self, event):
        if self.fechar():
            if not self.dialog:
                try:
                    self.window_list.remove(self)
                except ValueError as ve:
                    logging.debug(
                        '[CadastroPadrao] Não foi possível remover a janela ' +
                        str(self) + ' da lista de janelas abertas.')

            else:
                try:
                    pass
                    # self.parent().refresh()
                except Exception as e:
                    logging.debug(
                        '[CadastroPadrao] Não foi possível recarregar a janela <parent>: '
                        + str(self.parent()))

            event.accept()
        else:
            event.ignore()
Example #13
0
 def add_project_item_spec_list_view(self):
     icon_size = 16
     self.addSeparator()
     self.addWidget(QLabel("Specific items"))
     self.addWidget(self.project_item_spec_list_view)
     remove_spec = QToolButton(self)
     remove_spec_icon = QIcon(":/icons/wrench_minus.svg").pixmap(
         icon_size, icon_size)
     remove_spec.setIcon(remove_spec_icon)
     remove_spec.clicked.connect(
         self._toolbox.remove_selected_specification)
     remove_spec.setToolTip(
         "<html><head/><body><p>Remove selected specific item from the project</p></body></html>"
     )
     self.addWidget(remove_spec)
     add_spec = QToolButton(self)
     add_spec_icon = QIcon(":/icons/wrench_plus.svg").pixmap(
         icon_size, icon_size)
     add_spec.setIcon(add_spec_icon)
     add_spec.setMenu(self._toolbox.add_specification_popup_menu)
     add_spec.setPopupMode(QToolButton.InstantPopup)
     add_spec.setToolTip(
         "<html><head/><body><p>Add new specific item to the project</p></body></html>"
     )
     self.addWidget(add_spec)
    def __init__(self, parent, name, data, debug_state):
        if not type(data) == BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data
        self.debug_state = debug_state

        QToolBar.__init__(self, parent)

        # TODO: Is there a cleaner way to do this?
        self.setStyleSheet("""
		QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;}
		QToolButton:disabled{color: palette(alternate-base)}
		""")

        self.actionRun = QAction("Run", self)
        self.actionRun.triggered.connect(lambda: self.perform_run())
        self.actionRun.setIcon(load_icon('run.svg'))

        self.actionRestart = QAction("Restart", self)
        self.actionRestart.triggered.connect(lambda: self.perform_restart())
        self.actionRestart.setIcon(load_icon('restart.svg'))

        self.actionQuit = QAction("Quit", self)
        self.actionQuit.triggered.connect(lambda: self.perform_quit())
        self.actionQuit.setIcon(load_icon('cancel.svg'))

        self.actionAttach = QAction("Attach", self)
        self.actionAttach.triggered.connect(lambda: self.perform_attach())
        self.actionAttach.setIcon(load_icon('connect.svg'))

        self.actionDetach = QAction("Detach", self)
        self.actionDetach.triggered.connect(lambda: self.perform_detach())
        self.actionDetach.setIcon(load_icon('disconnect.svg'))

        self.actionSettings = QAction("Settings...", self)
        self.actionSettings.triggered.connect(lambda: self.perform_settings())

        self.actionPause = QAction("Pause", self)
        self.actionPause.triggered.connect(lambda: self.perform_pause())
        self.actionPause.setIcon(load_icon('pause.svg'))

        self.actionResume = QAction("Resume", self)
        self.actionResume.triggered.connect(lambda: self.perform_resume())
        self.actionResume.setIcon(load_icon('resume.svg'))

        self.actionStepIntoAsm = QAction("Step Into (Assembly)", self)
        self.actionStepIntoAsm.triggered.connect(
            lambda: self.perform_step_into_asm())
        self.actionStepIntoAsm.setIcon(load_icon('stepinto.svg'))

        self.actionStepIntoIL = QAction("Step Into", self)
        self.actionStepIntoIL.triggered.connect(
            lambda: self.perform_step_into_il())
        self.actionStepIntoIL.setIcon(load_icon('stepinto.svg'))

        self.actionStepOverAsm = QAction("Step Over (Assembly)", self)
        self.actionStepOverAsm.triggered.connect(
            lambda: self.perform_step_over_asm())
        self.actionStepOverAsm.setIcon(load_icon('stepover.svg'))

        self.actionStepOverIL = QAction("Step Over", self)
        self.actionStepOverIL.triggered.connect(
            lambda: self.perform_step_over_il())
        self.actionStepOverIL.setIcon(load_icon('stepover.svg'))

        self.actionStepReturn = QAction("Step Return", self)
        self.actionStepReturn.triggered.connect(
            lambda: self.perform_step_return())
        self.actionStepReturn.setIcon(load_icon('stepout.svg'))

        # session control menu
        self.controlMenu = QMenu("Process Control", self)
        self.controlMenu.addAction(self.actionRun)
        self.controlMenu.addAction(self.actionRestart)
        self.controlMenu.addAction(self.actionQuit)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionAttach)
        self.controlMenu.addAction(self.actionDetach)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionSettings)

        self.stepIntoMenu = QMenu("Step Into", self)
        self.stepIntoMenu.addAction(self.actionStepIntoIL)
        self.stepIntoMenu.addAction(self.actionStepIntoAsm)

        self.stepOverMenu = QMenu("Step Over", self)
        self.stepOverMenu.addAction(self.actionStepOverIL)
        self.stepOverMenu.addAction(self.actionStepOverAsm)

        self.btnControl = QToolButton(self)
        self.btnControl.setMenu(self.controlMenu)
        self.btnControl.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnControl.setDefaultAction(self.actionRun)
        self.addWidget(self.btnControl)

        # execution control buttons
        self.btnPauseResume = QToolButton(self)
        self.btnPauseResume.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnPauseResume.setDefaultAction(self.actionPause)
        self.addWidget(self.btnPauseResume)

        #self.addAction(self.actionPause)
        #self.addAction(self.actionResume)

        self.btnStepInto = QToolButton(self)
        self.btnStepInto.setMenu(self.stepIntoMenu)
        self.btnStepInto.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepInto.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepInto.setDefaultAction(self.actionStepIntoIL)
        self.addWidget(self.btnStepInto)

        self.btnStepOver = QToolButton(self)
        self.btnStepOver.setMenu(self.stepOverMenu)
        self.btnStepOver.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepOver.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepOver.setDefaultAction(self.actionStepOverIL)
        self.addWidget(self.btnStepOver)

        # TODO: Step until returning from current function
        self.btnStepReturn = QToolButton(self)
        self.btnStepReturn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepReturn.setDefaultAction(self.actionStepReturn)
        self.addWidget(self.btnStepReturn)

        #self.addAction(self.actionStepReturn)

        self.threadMenu = QMenu("Threads", self)

        self.btnThreads = QToolButton(self)
        self.btnThreads.setMenu(self.threadMenu)
        self.btnThreads.setPopupMode(QToolButton.InstantPopup)
        self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.addWidget(self.btnThreads)

        self.set_thread_list([])

        self.editStatus = QLineEdit('INACTIVE', self)
        self.editStatus.setReadOnly(True)
        self.editStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.addWidget(self.editStatus)

        # disable buttons
        self.set_actions_enabled(Run=self.can_exec(),
                                 Restart=False,
                                 Quit=False,
                                 Attach=self.can_connect(),
                                 Detach=False,
                                 Pause=False,
                                 Resume=False,
                                 StepInto=False,
                                 StepOver=False,
                                 StepReturn=False)
        self.set_resume_pause_action("Pause")
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
class DebugControlsWidget(QToolBar):
    def __init__(self, parent, name, data, debug_state):
        if not type(data) == BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data
        self.debug_state = debug_state

        QToolBar.__init__(self, parent)

        # TODO: Is there a cleaner way to do this?
        self.setStyleSheet("""
		QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;}
		QToolButton:disabled{color: palette(alternate-base)}
		""")

        self.actionRun = QAction("Run", self)
        self.actionRun.triggered.connect(lambda: self.perform_run())
        self.actionRun.setIcon(load_icon('run.svg'))

        self.actionRestart = QAction("Restart", self)
        self.actionRestart.triggered.connect(lambda: self.perform_restart())
        self.actionRestart.setIcon(load_icon('restart.svg'))

        self.actionQuit = QAction("Quit", self)
        self.actionQuit.triggered.connect(lambda: self.perform_quit())
        self.actionQuit.setIcon(load_icon('cancel.svg'))

        self.actionAttach = QAction("Attach", self)
        self.actionAttach.triggered.connect(lambda: self.perform_attach())
        self.actionAttach.setIcon(load_icon('connect.svg'))

        self.actionDetach = QAction("Detach", self)
        self.actionDetach.triggered.connect(lambda: self.perform_detach())
        self.actionDetach.setIcon(load_icon('disconnect.svg'))

        self.actionSettings = QAction("Settings...", self)
        self.actionSettings.triggered.connect(lambda: self.perform_settings())

        self.actionPause = QAction("Pause", self)
        self.actionPause.triggered.connect(lambda: self.perform_pause())
        self.actionPause.setIcon(load_icon('pause.svg'))

        self.actionResume = QAction("Resume", self)
        self.actionResume.triggered.connect(lambda: self.perform_resume())
        self.actionResume.setIcon(load_icon('resume.svg'))

        self.actionStepIntoAsm = QAction("Step Into (Assembly)", self)
        self.actionStepIntoAsm.triggered.connect(
            lambda: self.perform_step_into_asm())
        self.actionStepIntoAsm.setIcon(load_icon('stepinto.svg'))

        self.actionStepIntoIL = QAction("Step Into", self)
        self.actionStepIntoIL.triggered.connect(
            lambda: self.perform_step_into_il())
        self.actionStepIntoIL.setIcon(load_icon('stepinto.svg'))

        self.actionStepOverAsm = QAction("Step Over (Assembly)", self)
        self.actionStepOverAsm.triggered.connect(
            lambda: self.perform_step_over_asm())
        self.actionStepOverAsm.setIcon(load_icon('stepover.svg'))

        self.actionStepOverIL = QAction("Step Over", self)
        self.actionStepOverIL.triggered.connect(
            lambda: self.perform_step_over_il())
        self.actionStepOverIL.setIcon(load_icon('stepover.svg'))

        self.actionStepReturn = QAction("Step Return", self)
        self.actionStepReturn.triggered.connect(
            lambda: self.perform_step_return())
        self.actionStepReturn.setIcon(load_icon('stepout.svg'))

        # session control menu
        self.controlMenu = QMenu("Process Control", self)
        self.controlMenu.addAction(self.actionRun)
        self.controlMenu.addAction(self.actionRestart)
        self.controlMenu.addAction(self.actionQuit)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionAttach)
        self.controlMenu.addAction(self.actionDetach)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionSettings)

        self.stepIntoMenu = QMenu("Step Into", self)
        self.stepIntoMenu.addAction(self.actionStepIntoIL)
        self.stepIntoMenu.addAction(self.actionStepIntoAsm)

        self.stepOverMenu = QMenu("Step Over", self)
        self.stepOverMenu.addAction(self.actionStepOverIL)
        self.stepOverMenu.addAction(self.actionStepOverAsm)

        self.btnControl = QToolButton(self)
        self.btnControl.setMenu(self.controlMenu)
        self.btnControl.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnControl.setDefaultAction(self.actionRun)
        self.addWidget(self.btnControl)

        # execution control buttons
        self.btnPauseResume = QToolButton(self)
        self.btnPauseResume.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnPauseResume.setDefaultAction(self.actionPause)
        self.addWidget(self.btnPauseResume)

        #self.addAction(self.actionPause)
        #self.addAction(self.actionResume)

        self.btnStepInto = QToolButton(self)
        self.btnStepInto.setMenu(self.stepIntoMenu)
        self.btnStepInto.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepInto.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepInto.setDefaultAction(self.actionStepIntoIL)
        self.addWidget(self.btnStepInto)

        self.btnStepOver = QToolButton(self)
        self.btnStepOver.setMenu(self.stepOverMenu)
        self.btnStepOver.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepOver.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepOver.setDefaultAction(self.actionStepOverIL)
        self.addWidget(self.btnStepOver)

        # TODO: Step until returning from current function
        self.btnStepReturn = QToolButton(self)
        self.btnStepReturn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepReturn.setDefaultAction(self.actionStepReturn)
        self.addWidget(self.btnStepReturn)

        #self.addAction(self.actionStepReturn)

        self.threadMenu = QMenu("Threads", self)

        self.btnThreads = QToolButton(self)
        self.btnThreads.setMenu(self.threadMenu)
        self.btnThreads.setPopupMode(QToolButton.InstantPopup)
        self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.addWidget(self.btnThreads)

        self.set_thread_list([])

        self.editStatus = QLineEdit('INACTIVE', self)
        self.editStatus.setReadOnly(True)
        self.editStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.addWidget(self.editStatus)

        # disable buttons
        self.set_actions_enabled(Run=self.can_exec(),
                                 Restart=False,
                                 Quit=False,
                                 Attach=self.can_connect(),
                                 Detach=False,
                                 Pause=False,
                                 Resume=False,
                                 StepInto=False,
                                 StepOver=False,
                                 StepReturn=False)
        self.set_resume_pause_action("Pause")
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")

    def __del__(self):
        # TODO: Move this elsewhere
        # This widget is tasked with cleaning up the state after the view is closed
        # binjaplug.delete_state(self.bv)
        pass

    # -------------------------------------------------------------------------
    # Helpers
    # -------------------------------------------------------------------------
    def can_exec(self):
        return DebugAdapter.ADAPTER_TYPE.use_exec(
            self.debug_state.adapter_type)

    def can_connect(self):
        return DebugAdapter.ADAPTER_TYPE.use_connect(
            self.debug_state.adapter_type)

    def alert_need_install(self, proc):
        message = "Cannot start debugger: {} not found on {}.".format(
            proc,
            "the target machine" if self.can_connect() else "your machine")

        adapter_type = self.debug_state.adapter_type

        # TODO: detect remote os correctly, as gdb/lldb are compatible with both macos and linux
        if adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_GDB or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_GDB:
            remote_os = "Linux"
        elif adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_LLDB or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_LLDB:
            remote_os = "Darwin"
        elif adapter_type == DebugAdapter.ADAPTER_TYPE.LOCAL_DBGENG or adapter_type == DebugAdapter.ADAPTER_TYPE.REMOTE_DBGENG:
            remote_os = "Windows"
        else:
            # Uncertain
            remote_os = platform.system()

        if remote_os == "Linux":
            message += "\nYou can find this in your package manager or build it from source."
        elif remote_os == "Darwin":
            if proc == "lldb":
                message += "\nYou need to install it by running the following command in Terminal:\nxcode-select --install"
            elif proc == "gdbserver":
                message += "\nYou can find this in your package manager or build it from source."
            else:
                message += "\nYou need to install this manually."
        elif remote_os == "Windows":
            # TODO: dbgeng does not currently throw this
            message += "\nYou need to reinstall the debugger plugin."
        else:
            message += "\nYou need to install this manually."

        show_message_box("Cannot Start Debugger",
                         message,
                         icon=MessageBoxIcon.ErrorIcon)

    # -------------------------------------------------------------------------
    # UIActions
    # -------------------------------------------------------------------------

    def perform_run(self):
        def perform_run_thread():
            while True:
                try:
                    self.debug_state.run()
                    execute_on_main_thread_and_wait(perform_run_after)
                except ConnectionRefusedError:
                    execute_on_main_thread_and_wait(
                        lambda: perform_run_error('ERROR: Connection Refused'))
                except DebugAdapter.NotExecutableError as e:
                    fpath = e.args[0]
                    if platform.system() != 'Windows':
                        msg = '%s is not executable, would you like to set +x and retry?' % fpath
                        res = show_message_box(
                            'Error', msg, MessageBoxButtonSet.YesNoButtonSet,
                            MessageBoxIcon.ErrorIcon)
                        if res == MessageBoxButtonResult.YesButton:
                            os.chmod(fpath, os.stat(fpath).st_mode | 0o100)
                            continue
                    execute_on_main_thread_and_wait(lambda: perform_run_error(
                        'ERROR: Target Not Executable'))
                except DebugAdapter.NotInstalledError as e:
                    execute_on_main_thread_and_wait(
                        lambda: self.alert_need_install(e.args[0]))
                    execute_on_main_thread_and_wait(lambda: perform_run_error(
                        'ERROR: Debugger Not Installed'))
                except DebugAdapter.PermissionDeniedError as e:
                    execute_on_main_thread_and_wait(
                        lambda: perform_run_error('ERROR: Permission denied'))
                    if platform.system() == 'Darwin':
                        res = show_message_box(
                            'Error',
                            'Developer tools need to be enabled to debug programs. This can be authorized either from here or by starting a debugger in Xcode.',
                            MessageBoxButtonSet.OKButtonSet,
                            MessageBoxIcon.ErrorIcon)
                except Exception as e:
                    execute_on_main_thread_and_wait(lambda: perform_run_error(
                        'ERROR: ' + ' '.join(e.args)))
                    traceback.print_exc(file=sys.stderr)

                break

        def perform_run_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_run_error(e):
            self.state_error(e)

        self.state_starting('STARTING')

        threading.Thread(target=perform_run_thread).start()

    def perform_restart(self):
        def perform_restart_thread():
            try:
                self.debug_state.restart()
                execute_on_main_thread_and_wait(perform_restart_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_restart_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(lambda: perform_restart_error(
                    'ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_restart_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_restart_error(e):
            self.state_error(e)

        self.state_starting('RESTARTING')
        threading.Thread(target=perform_restart_thread).start()

    def perform_quit(self):
        self.debug_state.quit()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_attach(self):
        def perform_attach_thread():
            try:
                self.debug_state.attach()
                execute_on_main_thread_and_wait(perform_attach_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except TimeoutError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_attach_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_attach_error(e):
            self.state_error(e)

        self.state_starting('ATTACHING')
        threading.Thread(target=perform_attach_thread).start()

    def perform_detach(self):
        self.debug_state.detach()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_settings(self):
        def settings_finished():
            if self.debug_state.running:
                self.state_running()
            elif self.debug_state.connected:
                local_rip = self.debug_state.local_ip
                if self.debug_state.bv.read(local_rip, 1) and len(
                        self.debug_state.bv.get_functions_containing(
                            local_rip)) > 0:
                    self.state_stopped()
                else:
                    self.state_stopped_extern()
            else:
                self.state_inactive()

        dialog = AdapterSettingsDialog.AdapterSettingsDialog(self, self.bv)
        dialog.show()
        dialog.finished.connect(settings_finished)

    def perform_pause(self):
        self.debug_state.pause()
        # Don't update state here-- one of the other buttons is running in a thread and updating for us

    def perform_resume(self):
        def perform_resume_thread():
            (reason, data) = self.debug_state.go()
            execute_on_main_thread_and_wait(
                lambda: perform_resume_after(reason, data))

        def perform_resume_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_running()
        threading.Thread(target=perform_resume_thread).start()

    def perform_step_into_asm(self):
        def perform_step_into_asm_thread():
            (reason, data) = self.debug_state.step_into()
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_asm_after(reason, data))

        def perform_step_into_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_asm_thread).start()

    def perform_step_into_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_into_il_thread():
            (reason, data) = self.debug_state.step_into(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_il_after(reason, data))

        def perform_step_into_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_il_thread).start()

    def perform_step_over_asm(self):
        def perform_step_over_asm_thread():
            (reason, data) = self.debug_state.step_over()
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_asm_after(reason, data))

        def perform_step_over_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_asm_thread).start()

    def perform_step_over_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_over_il_thread():
            (reason, data) = self.debug_state.step_over(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_il_after(reason, data))

        def perform_step_over_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_il_thread).start()

    def perform_step_return(self):
        def perform_step_return_thread():
            (reason, data) = self.debug_state.step_return()
            execute_on_main_thread_and_wait(
                lambda: perform_step_return_after(reason, data))

        def perform_step_return_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_return_thread).start()

    # -------------------------------------------------------------------------
    # Control state setters
    # -------------------------------------------------------------------------

    def set_actions_enabled(self, **kwargs):
        def enable_step_into(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)

        def enable_step_over(e):
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)

        def enable_starting(e):
            self.actionRun.setEnabled(e and self.can_exec())
            self.actionAttach.setEnabled(e and self.can_connect())

        def enable_stopping(e):
            self.actionRestart.setEnabled(e)
            self.actionQuit.setEnabled(e)
            self.actionDetach.setEnabled(e)

        def enable_stepping(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)
            self.actionStepReturn.setEnabled(e)

        actions = {
            "Run": lambda e: self.actionRun.setEnabled(e),
            "Restart": lambda e: self.actionRestart.setEnabled(e),
            "Quit": lambda e: self.actionQuit.setEnabled(e),
            "Attach": lambda e: self.actionAttach.setEnabled(e),
            "Detach": lambda e: self.actionDetach.setEnabled(e),
            "Pause": lambda e: self.actionPause.setEnabled(e),
            "Resume": lambda e: self.actionResume.setEnabled(e),
            "StepInto": enable_step_into,
            "StepOver": enable_step_over,
            "StepReturn": lambda e: self.actionStepReturn.setEnabled(e),
            "Threads": lambda e: self.btnThreads.setEnabled(e),
            "Starting": enable_starting,
            "Stopping": enable_stopping,
            "Stepping": enable_stepping,
        }
        for (action, enabled) in kwargs.items():
            actions[action](enabled)

    def set_default_process_action(self, action):
        actions = {
            "Run": self.actionRun,
            "Restart": self.actionRestart,
            "Quit": self.actionQuit,
            "Attach": self.actionAttach,
            "Detach": self.actionDetach,
        }
        self.btnControl.setDefaultAction(actions[action])

    def set_resume_pause_action(self, action):
        lookup = {'Resume': self.actionResume, 'Pause': self.actionPause}
        self.btnPauseResume.setDefaultAction(lookup[action])

    def set_thread_list(self, threads):
        def select_thread_fn(tid):
            def select_thread(tid):
                stateObj = binjaplug.get_state(self.bv)
                if stateObj.connected and not stateObj.running:
                    stateObj.threads.current = tid
                    stateObj.ui.context_display()
                    stateObj.ui.on_step()
                else:
                    print('cannot set thread in current state')

            return lambda: select_thread(tid)

        self.threadMenu.clear()
        if len(threads) > 0:
            for thread in threads:
                item_name = "Thread {} at {}".format(thread['tid'],
                                                     hex(thread['ip']))
                action = self.threadMenu.addAction(
                    item_name, select_thread_fn(thread['tid']))
                if thread['selected']:
                    self.btnThreads.setDefaultAction(action)
        else:
            defaultThreadAction = self.threadMenu.addAction("Thread List")
            defaultThreadAction.setEnabled(False)
            self.btnThreads.setDefaultAction(defaultThreadAction)

    # -------------------------------------------------------------------------
    # State handling
    # -------------------------------------------------------------------------

    def state_starting(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=False,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_inactive(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_stopped(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_stopped_extern(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 StepReturn=False,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_running(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_busy(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_error(self, msg=None):
        self.editStatus.setText(msg or 'ERROR')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Pause=False,
                                 Resume=False,
                                 Stepping=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Resume")

    def handle_stop_return(self, reason, data):
        if reason == DebugAdapter.STOP_REASON.STDOUT_MESSAGE:
            self.state_stopped('stdout: ' + data)
        elif reason == DebugAdapter.STOP_REASON.PROCESS_EXITED:
            self.debug_state.quit()
            self.state_inactive('process exited, return code=%d' % data)
        elif reason == DebugAdapter.STOP_REASON.BACKEND_DISCONNECTED:
            self.debug_state.quit()
            self.state_inactive('backend disconnected (process exited?)')
Example #16
0
    def __init__(self, original, processed, title=None, parent=None):
        super(ImageViewer, self).__init__(parent)
        if original is None and processed is None:
            raise ValueError(
                self.tr('ImageViewer.__init__: Empty image received'))
        if original is None and processed is not None:
            original = processed
        self.original = original
        self.processed = processed
        if self.original is not None and self.processed is None:
            self.view = DynamicView(self.original)
        else:
            self.view = DynamicView(self.processed)

        # view_label = QLabel(self.tr('View:'))
        self.original_radio = QRadioButton(self.tr('Original'))
        self.original_radio.setToolTip(
            self.tr('Show the original image for comparison'))
        self.process_radio = QRadioButton(self.tr('Processed'))
        self.process_radio.setToolTip(
            self.tr('Show result of the current processing'))
        self.zoom_label = QLabel()
        full_button = QToolButton()
        full_button.setText(self.tr('100%'))
        fit_button = QToolButton()
        fit_button.setText(self.tr('Fit'))
        height, width, _ = self.original.shape
        size_label = QLabel(self.tr('[{}x{} px]'.format(height, width)))
        export_button = QToolButton()
        export_button.setText(self.tr('Export...'))

        tool_layout = QHBoxLayout()
        if processed is not None:
            # tool_layout.addWidget(view_label)
            tool_layout.addWidget(self.original_radio)
            tool_layout.addWidget(self.process_radio)
            tool_layout.addStretch()
        tool_layout.addWidget(QLabel(self.tr('Zoom:')))
        tool_layout.addWidget(self.zoom_label)
        # tool_layout.addWidget(full_button)
        # tool_layout.addWidget(fit_button)
        tool_layout.addStretch()
        tool_layout.addWidget(size_label)
        if processed is not None:
            tool_layout.addWidget(export_button)
            self.original_radio.setChecked(False)
            self.process_radio.setChecked(True)
            self.toggle_mode(False)

        vert_layout = QVBoxLayout()
        if title is not None:
            title_label = QLabel(title)
            modify_font(title_label, bold=True)
            title_label.setAlignment(Qt.AlignCenter)
            vert_layout.addWidget(title_label)
        vert_layout.addWidget(self.view)
        vert_layout.addLayout(tool_layout)
        self.setLayout(vert_layout)

        self.original_radio.toggled.connect(self.toggle_mode)
        fit_button.clicked.connect(self.view.zoom_fit)
        full_button.clicked.connect(self.view.zoom_full)
        export_button.clicked.connect(self.export_image)
        self.view.view_changed.connect(self.forward_changed)
Example #17
0
    def __init__(self,
                 table,
                 headers,
                 bold=True,
                 mono=True,
                 tooltips=None,
                 align=False,
                 search=True,
                 parent=None):
        super(TableWidget, self).__init__(parent)

        self.table_widget = QTableWidget(len(table), len(table[0]))
        for i, row in enumerate(table):
            for j, item in enumerate(row):
                if item is not None:
                    self.table_widget.setItem(i, j,
                                              QTableWidgetItem(str(item)))
                    if tooltips is not None:
                        self.table_widget.setToolTip(tooltips[i][j])
                    modify_font(self.table_widget.item(i, j),
                                bold=bold and j == 0,
                                mono=mono)
                    if align:
                        self.table_widget.item(i, j).setTextAlignment(
                            Qt.AlignRight)

        self.table_headers = headers
        self.table_widget.setHorizontalHeaderLabels(self.table_headers)
        self.table_widget.setSelectionMode(QAbstractItemView.SingleSelection)
        self.table_widget.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.table_widget.resizeColumnsToContents()
        self.table_widget.setAlternatingRowColors(True)
        self.table_widget.itemDoubleClicked.connect(self.copy)

        search_layout = QHBoxLayout()
        search_layout.addWidget(QLabel(self.tr('Search:')))
        self.search_edit = QLineEdit()
        self.search_edit.textChanged.connect(self.start)
        self.search_edit.returnPressed.connect(self.next)
        search_layout.addWidget(self.search_edit)

        clear_button = QToolButton()
        clear_button.setIcon(QIcon('icons/clear.svg'))
        clear_button.setShortcut(QKeySequence.DeleteCompleteLine)
        clear_button.setToolTip(self.tr('Clear pattern'))
        clear_button.clicked.connect(self.search_edit.clear)
        search_layout.addWidget(clear_button)

        self.case_button = QToolButton()
        self.case_button.setText(self.tr('Aa'))
        self.case_button.setCheckable(True)
        self.case_button.toggled.connect(self.start)
        self.case_button.setToolTip(self.tr('Case sensitive'))
        search_layout.addWidget(self.case_button)

        self.word_button = QToolButton()
        self.word_button.setText(self.tr('W'))
        self.word_button.setCheckable(True)
        self.word_button.toggled.connect(self.start)
        self.word_button.setToolTip(self.tr('Whole words'))
        search_layout.addWidget(self.word_button)

        self.regex_button = QToolButton()
        self.regex_button.setText(self.tr('.*'))
        self.regex_button.setCheckable(True)
        self.regex_button.toggled.connect(self.start)
        self.regex_button.setToolTip(self.tr('Regular expression'))
        search_layout.addWidget(self.regex_button)

        prev_button = QToolButton()
        prev_button.setIcon(QIcon('icons/up.svg'))
        prev_button.setShortcut(QKeySequence.FindPrevious)
        prev_button.clicked.connect(self.previous)
        prev_button.setToolTip(self.tr('Previous occurence'))
        search_layout.addWidget(prev_button)

        next_button = QToolButton()
        next_button.setIcon(QIcon('icons/down.svg'))
        next_button.setShortcut(QKeySequence.FindNext)
        next_button.clicked.connect(self.next)
        next_button.setToolTip(self.tr('Next occurence'))
        search_layout.addWidget(next_button)

        self.matches_label = QLabel()
        search_layout.addWidget(self.matches_label)
        search_layout.addStretch()

        export_button = QToolButton()
        export_button.setText(self.tr('Export...'))
        export_button.clicked.connect(self.export)
        search_layout.addWidget(export_button)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.table_widget)
        if search:
            main_layout.addLayout(search_layout)
        self.setLayout(main_layout)
class TabBarPlus(QTabBar):
    """Tab bar that has a plus button floating to the right of the tabs."""

    plus_clicked = Signal()

    def __init__(self, parent):
        """
        Args:
            parent (MultiSpineDBEditor)
        """
        super().__init__(parent)
        self._parent = parent
        self._plus_button = QToolButton(self)
        self._plus_button.setIcon(QIcon(CharIconEngine("\uf067")))
        self._plus_button.clicked.connect(
            lambda _=False: self.plus_clicked.emit())
        self._move_plus_button()
        self.setShape(QTabBar.RoundedNorth)
        self.setTabsClosable(True)
        self.setMovable(True)
        self.setElideMode(Qt.ElideLeft)
        self.drag_index = None
        self._tab_hot_spot_x = None
        self._hot_spot_y = None

    def resizeEvent(self, event):
        """Sets the dimension of the plus button. Also, makes the tab bar as wide as the parent."""
        super().resizeEvent(event)
        self.setFixedWidth(self.parent().width())
        self.setMinimumHeight(self.height())
        self._move_plus_button()
        extent = max(0, self.height() - 2)
        self._plus_button.setFixedSize(extent, extent)
        self.setExpanding(False)

    def tabLayoutChange(self):
        super().tabLayoutChange()
        self._move_plus_button()

    def _move_plus_button(self):
        """Places the plus button at the right of the last tab."""
        left = sum([self.tabRect(i).width() for i in range(self.count())])
        top = self.geometry().top() + 1
        self._plus_button.move(left, top)

    def mousePressEvent(self, event):
        """Registers the position of the press, in case we need to detach the tab."""
        super().mousePressEvent(event)
        tab_rect = self.tabRect(self.tabAt(event.pos()))
        self._tab_hot_spot_x = event.pos().x() - tab_rect.x()
        self._hot_spot_y = event.pos().y() - tab_rect.y()

    def mouseMoveEvent(self, event):
        """Detaches a tab either if the user moves beyond the limits of the tab bar, or if it's the only one."""
        self._plus_button.hide()
        if self.count() == 1:
            self._send_release_event(event.pos())
            hot_spot = QPoint(event.pos().x(), self._hot_spot_y)
            self._parent.start_drag(hot_spot)
            return
        if self.count() > 1 and not self.geometry().contains(event.pos()):
            self._send_release_event(event.pos())
            hot_spot_x = event.pos().x()
            hot_spot = QPoint(event.pos().x(), self._hot_spot_y)
            index = self.tabAt(hot_spot)
            if index == -1:
                index = self.count() - 1
            self._parent.detach(index, hot_spot,
                                hot_spot_x - self._tab_hot_spot_x)
            return
        super().mouseMoveEvent(event)

    def _send_release_event(self, pos):
        """Sends a mouse release event at given position in local coordinates. Called just before detaching a tab.

        Args:
            pos (QPoint)
        """
        self.drag_index = None
        release_event = QMouseEvent(QEvent.MouseButtonRelease, pos,
                                    Qt.LeftButton, Qt.LeftButton,
                                    Qt.NoModifier)
        QApplication.sendEvent(self, release_event)

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        self._plus_button.show()
        self.update()
        self.releaseMouse()
        if self.drag_index is not None:
            # Pass it to parent
            event.ignore()

    def start_dragging(self, index):
        """Stars dragging the given index. This happens when a detached tab is reattached to this bar.

        Args:
            index (int)
        """
        self.drag_index = index
        press_pos = self.tabRect(self.drag_index).center()
        press_event = QMouseEvent(QEvent.MouseButtonPress, press_pos,
                                  Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
        QApplication.sendEvent(self, press_event)
        QApplication.processEvents()
        move_pos = self.mapFromGlobal(QCursor.pos())
        if self.geometry().contains(move_pos):
            move_event = QMouseEvent(QEvent.MouseMove, move_pos, Qt.LeftButton,
                                     Qt.LeftButton, Qt.NoModifier)
            QApplication.sendEvent(self, move_event)
        self.grabMouse()

    def index_under_mouse(self):
        """Returns the index under the mouse cursor, or None if the cursor isn't over the tab bar.
        Used to check for drop targets.

        Returns:
            int or NoneType
        """
        pos = self.mapFromGlobal(QCursor.pos())
        if not self.geometry().contains(pos):
            return None
        index = self.tabAt(pos)
        if index == -1:
            index = self.count()
        return index

    def _show_plus_button_context_menu(self, global_pos):
        toolbox = self._parent.db_mngr.parent()
        if toolbox is None:
            return
        ds_urls = {
            ds.name: ds.project_item.sql_alchemy_url()
            for ds in toolbox.project_item_model.items("Data Stores")
        }
        if not ds_urls:
            return
        menu = QMenu(self)
        for name, url in ds_urls.items():
            action = menu.addAction(name,
                                    lambda name=name, url=url: self._parent.
                                    add_new_tab({url: name}))
            action.setEnabled(bool(url))
        menu.popup(global_pos)
        menu.aboutToHide.connect(menu.deleteLater)

    def contextMenuEvent(self, event):
        index = self.tabAt(event.pos())
        if self._plus_button.underMouse():
            self._show_plus_button_context_menu(event.globalPos())
            return
        if self.tabButton(index, QTabBar.RightSide).underMouse():
            return
        db_editor = self._parent.tab_widget.widget(index)
        if db_editor is None:
            return
        menu = QMenu(self)
        others = self._parent.others()
        if others:
            move_tab_menu = menu.addMenu("Move tab to another window")
            move_tab_to_new_window = move_tab_menu.addAction(
                "New window",
                lambda _=False, index=index: self._parent.move_tab(
                    index, None))
            for other in others:
                move_tab_menu.addAction(
                    other.name(),
                    lambda _=False, index=index, other=other: self._parent.
                    move_tab(index, other))
        else:
            move_tab_to_new_window = menu.addAction(
                "Move tab to new window",
                lambda _=False, index=index: self._parent.move_tab(
                    index, None))
        move_tab_to_new_window.setEnabled(self.count() > 1)
        menu.addSeparator()
        menu.addAction(db_editor.url_toolbar.reload_action)
        db_url_codenames = db_editor.db_url_codenames
        menu.addAction(
            QIcon(CharIconEngine("\uf24d")),
            "Duplicate",
            lambda _=False, index=index + 1, db_url_codenames=db_url_codenames:
            self._parent.insert_new_tab(index, db_url_codenames),
        )
        menu.popup(event.globalPos())
        menu.aboutToHide.connect(menu.deleteLater)
        event.accept()
Example #19
0
class QtViewer(QWidget):
    """Full featured, stand-alone viewer suitable for displaying a :class:`vpype.Document` to
    a user."""
    def __init__(
        self,
        document: Optional[vp.Document] = None,
        view_mode: ViewMode = ViewMode.PREVIEW,
        show_pen_up: bool = False,
        show_points: bool = False,
        parent=None,
    ):
        super().__init__(parent)

        self.setWindowTitle("vpype viewer")
        self.setStyleSheet("""
        QToolButton:pressed {
            background-color: rgba(0, 0, 0, 0.2);
        }
        """)

        self._viewer_widget = QtViewerWidget(parent=self)

        # setup toolbar
        self._toolbar = QToolBar()
        self._icon_size = QSize(32, 32)
        self._toolbar.setIconSize(self._icon_size)

        view_mode_grp = QActionGroup(self._toolbar)
        if _DEBUG_ENABLED:
            act = view_mode_grp.addAction("None")
            act.setCheckable(True)
            act.setChecked(view_mode == ViewMode.NONE)
            act.triggered.connect(
                functools.partial(self.set_view_mode, ViewMode.NONE))
        act = view_mode_grp.addAction("Outline Mode")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.OUTLINE)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.OUTLINE))
        act = view_mode_grp.addAction("Outline Mode (Colorful)")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.OUTLINE_COLORFUL)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.OUTLINE_COLORFUL))
        act = view_mode_grp.addAction("Preview Mode")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.PREVIEW)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.PREVIEW))
        self.set_view_mode(view_mode)

        # VIEW MODE
        # view modes
        view_mode_btn = QToolButton()
        view_mode_menu = QMenu(view_mode_btn)
        act = view_mode_menu.addAction("View Mode:")
        act.setEnabled(False)
        view_mode_menu.addActions(view_mode_grp.actions())
        view_mode_menu.addSeparator()
        # show pen up
        act = view_mode_menu.addAction("Show Pen-Up Trajectories")
        act.setCheckable(True)
        act.setChecked(show_pen_up)
        act.toggled.connect(self.set_show_pen_up)
        self._viewer_widget.engine.show_pen_up = show_pen_up
        # show points
        act = view_mode_menu.addAction("Show Points")
        act.setCheckable(True)
        act.setChecked(show_points)
        act.toggled.connect(self.set_show_points)
        self._viewer_widget.engine.show_points = show_points
        # preview mode options
        view_mode_menu.addSeparator()
        act = view_mode_menu.addAction("Preview Mode Options:")
        act.setEnabled(False)
        # pen width
        pen_width_menu = view_mode_menu.addMenu("Pen Width")
        act_grp = PenWidthActionGroup(0.3, parent=pen_width_menu)
        act_grp.triggered.connect(self.set_pen_width_mm)
        pen_width_menu.addActions(act_grp.actions())
        self.set_pen_width_mm(0.3)
        # pen opacity
        pen_opacity_menu = view_mode_menu.addMenu("Pen Opacity")
        act_grp = PenOpacityActionGroup(0.8, parent=pen_opacity_menu)
        act_grp.triggered.connect(self.set_pen_opacity)
        pen_opacity_menu.addActions(act_grp.actions())
        self.set_pen_opacity(0.8)
        # debug view
        if _DEBUG_ENABLED:
            act = view_mode_menu.addAction("Debug View")
            act.setCheckable(True)
            act.toggled.connect(self.set_debug)

        view_mode_btn.setMenu(view_mode_menu)
        view_mode_btn.setIcon(load_icon("eye-outline.svg"))
        view_mode_btn.setText("View")
        view_mode_btn.setPopupMode(QToolButton.InstantPopup)
        view_mode_btn.setStyleSheet(
            "QToolButton::menu-indicator { image: none; }")
        self._toolbar.addWidget(view_mode_btn)

        # LAYER VISIBILITY
        self._layer_visibility_btn = QToolButton()
        self._layer_visibility_btn.setIcon(
            load_icon("layers-triple-outline.svg"))
        self._layer_visibility_btn.setText("Layer")
        self._layer_visibility_btn.setMenu(QMenu(self._layer_visibility_btn))
        self._layer_visibility_btn.setPopupMode(QToolButton.InstantPopup)
        self._layer_visibility_btn.setStyleSheet(
            "QToolButton::menu-indicator { image: none; }")
        self._toolbar.addWidget(self._layer_visibility_btn)

        # FIT TO PAGE
        fit_act = self._toolbar.addAction(load_icon("fit-to-page-outline.svg"),
                                          "Fit")
        fit_act.triggered.connect(self._viewer_widget.engine.fit_to_viewport)

        # RULER
        # TODO: not implemented yet
        # self._toolbar.addAction(load_icon("ruler-square.svg"), "Units")

        # MOUSE COORDINATES>
        self._mouse_coord_lbl = QLabel("")
        font = self._mouse_coord_lbl.font()
        font.setPointSize(11)
        self._mouse_coord_lbl.setMargin(6)
        # self._mouse_coord_lbl.setStyleSheet("QLabel { background-color : red }")
        self._mouse_coord_lbl.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        self._mouse_coord_lbl.setSizePolicy(QSizePolicy.Expanding,
                                            QSizePolicy.Minimum)
        self._mouse_coord_lbl.setFont(font)
        self._toolbar.addWidget(self._mouse_coord_lbl)
        # noinspection PyUnresolvedReferences
        self._viewer_widget.mouse_coords.connect(
            self.set_mouse_coords)  # type: ignore

        # setup layout
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setMargin(0)
        layout.addWidget(self._toolbar)
        layout.addWidget(self._viewer_widget)
        self.setLayout(layout)

        if document is not None:
            self.set_document(document)

    def set_document(self, document: vp.Document) -> None:
        self._viewer_widget.set_document(document)
        self._update_layer_menu()

    def _update_layer_menu(self):
        layer_menu = QMenu(self._layer_visibility_btn)
        for layer_id in sorted(self._viewer_widget.document().layers):
            action = layer_menu.addAction(f"Layer {layer_id}")
            action.setCheckable(True)
            action.setChecked(True)
            # TODO: set color icon
            action.triggered.connect(
                functools.partial(
                    self._viewer_widget.engine.toggle_layer_visibility,
                    layer_id))
        self._layer_visibility_btn.setMenu(layer_menu)

    def set_mouse_coords(self, txt: str) -> None:
        self._mouse_coord_lbl.setText(txt)

    def set_view_mode(self, mode: ViewMode) -> None:
        self._viewer_widget.engine.view_mode = mode

    def set_show_pen_up(self, show_pen_up: bool) -> None:
        self._viewer_widget.engine.show_pen_up = show_pen_up

    def set_show_points(self, show_points: bool) -> None:
        self._viewer_widget.engine.show_points = show_points

    def set_pen_width_mm(self, value: Union[float, QAction]) -> None:
        if isinstance(value, QAction):
            value = value.data()
        self._viewer_widget.engine.pen_width = value / 25.4 * 96.0

    def set_pen_opacity(self, value: Union[float, QAction]) -> None:
        if isinstance(value, QAction):
            value = value.data()
        self._viewer_widget.engine.pen_opacity = value

    def set_debug(self, debug: bool) -> None:
        self._viewer_widget.engine.debug = debug
Example #20
0
class ModernWindow(QDialog):
    """
    Implements a modern window-frame.
    Notes:
        * Except for macOS, the OS will not add a shadow to the window. On Windows and Linux this
          class will add QGraphicsDropShadowEffect to the entire window.
    """
    __double_clicked = Signal()

    def __init__(self, window: Any, parent: Optional[Any],
                 style: Optional[Any], title_bar: bool,
                 transparent_window: bool, titlebar_height: Optional[int],
                 titlebar_color: Optional[QColor],
                 titlebar_nofocus_color: Optional[QColor],
                 titlebar_text_color: Optional[QColor],
                 titlebar_widget: Optional[Union[QWidget, QTabWidget]],
                 window_buttons_position: Optional[str]) -> None:
        """
        Constructor.
        Parameters:
         * window: Qt widget that should be wrapped
         * window_style: choose from one of the pre-defined styles, e.g. 'APL', 'WOW'
         * title_bar: display a traditional titlebar or not
         * titlebar_height: height of the titlebar in pixel
         * titlebar_color: override background color of the titlebar
         * titlebar_nofocus_color: override background color of the titlebar when the window is out of focus
         * titlebar_text_color: override color for the window title text
         * window_button_position: positions the window close/min/max buttons left (macOS) or right of the window title
           (Windows, Gnome, KDE, etc.)
         * transparent_window: turns off window transparency. This ensures compatibility with certain widgets and draw
           modes, such as QMediaWidget on Windows. Drawbacks: when transparent_window is False, window drop shadow
           effects will be disabled, except for operating systems that automatically add a drop shadow to all windows.
        """
        def expose_msgbox_methods() -> None:
            """ensure Qt methods of wrapped children are exposed"""
            assert self.__window is not None
            self.setText = self.__window.setText
            self.setInformativeText = self.__window.setInformativeText
            self.setDetailedText = self.__window.setDetailedText

        def add_window_buttons() -> None:
            """create window widget buttons"""
            button_size_policy = QSizePolicy(QSizePolicy.Fixed,
                                             QSizePolicy.Fixed)
            if not isinstance(self.__window, QDIALOG_TYPES):
                self._minimize_button = QToolButton(self)
                self._minimize_button.setObjectName('btnMinimize')
                self._minimize_button.setSizePolicy(button_size_policy)
                self._minimize_button.clicked.connect(
                    self.__on_minimize_button_clicked)  # pylint: disable=no-member
                self._restore_button = QToolButton(self)
                self._restore_button.setObjectName('btnRestore')
                self._restore_button.setSizePolicy(button_size_policy)
                self._restore_button.clicked.connect(
                    self.__on_restore_button_clicked)  # pylint: disable=no-member
                self._maximize_button = QToolButton(self)
                self._maximize_button.setObjectName('btnMaximize')
                self._maximize_button.setSizePolicy(button_size_policy)
                self._maximize_button.clicked.connect(
                    self.__on_maximize_button_clicked)  # pylint: disable=no-member
            self._close_button = QToolButton(self)
            self._close_button.setObjectName('btnClose')
            self._close_button.setSizePolicy(button_size_policy)
            self._close_button.clicked.connect(self.__on_close_button_clicked)  # pylint: disable=no-member
            self.__move_window_buttons()  # place buttons

        def set_window_properties(titlebar_height: int) -> None:
            """Sets Qt Properties for the wrapper window"""
            assert self.__window is not None
            self.setStyleSheet(self.__style.get_window_stylesheet())
            self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint
                                | Qt.WindowSystemMenuHint
                                | Qt.WindowCloseButtonHint
                                | Qt.WindowMinimizeButtonHint
                                | Qt.WindowMaximizeButtonHint)
            if sys.platform not in ["darwin"] and self.__transparent_window:
                self.setAttribute(Qt.WA_TranslucentBackground,
                                  True)  # no need for translucency on macOS
            if sys.platform == 'win32':
                self.setAttribute(
                    Qt.WA_OpaquePaintEvent,
                    True)  # avoid flickering on window resize on Windows
            else:
                self.setAttribute(Qt.WA_OpaquePaintEvent, False)
            self.setAttribute(Qt.WA_NoSystemBackground, True)
            self.setWindowTitle(self.__window.windowTitle())
            self.setGeometry(self.__window.geometry())
            height = titlebar_height
            if self.__use_shadow:
                height += self.__style.window.SHADOW_RADIUS_PX * 2
            self.setMinimumHeight(self.__window.minimumSizeHint().height() +
                                  height)
            self.setMinimumWidth(self.__window.minimumSizeHint().width())

        def add_window_drop_shadow() -> None:
            """Adds a drop-shadow behind the window"""
            if self.__use_shadow:
                self.layout().setMargin(self.__style.window.SHADOW_RADIUS_PX)
                drop_shadow_effect = QGraphicsDropShadowEffect(self)
                drop_shadow_effect.setEnabled(True)
                drop_shadow_effect.setBlurRadius(
                    self.__style.window.SHADOW_RADIUS_PX)
                color = QColor(self.__style.window.SHADOW_COLOR_RGB)
                color.setAlpha(self.__style.window.SHADOW_OPACITY_HEX)
                drop_shadow_effect.setColor(color)
                drop_shadow_effect.setOffset(0)
                self.setGraphicsEffect(drop_shadow_effect)

        def adjust_wrapped_window_object() -> None:
            """Adding attribute to clean up the parent window when the child is closed"""
            assert self.__window is not None
            self.__window.wrapper = self
            self.__window.setAttribute(Qt.WA_DeleteOnClose, True)
            self.__window.destroyed.connect(self.__child_was_closed)

        def add_resizers() -> None:
            """Adds resizer widgets, which act as resize handles, to the window wrapper"""
            if isinstance(window, QDIALOG_TYPES):
                assert self.__window is not None
                self.__window.installEventFilter(self)
            else:
                self.resizer_top = Resizer(
                    self, Resizer.TOP, self.__style.window.SHADOW_RADIUS_PX)
                self.resizer_bot = Resizer(
                    self, Resizer.BOTTOM, self.__style.window.SHADOW_RADIUS_PX)
                self.resizer_lef = Resizer(
                    self, Resizer.LEFT, self.__style.window.SHADOW_RADIUS_PX)
                self.resizer_rig = Resizer(
                    self, Resizer.RIGHT, self.__style.window.SHADOW_RADIUS_PX)
                self.resizer_tl = Resizer(self, Resizer.TOP_LEFT,
                                          self.__style.window.SHADOW_RADIUS_PX)
                self.resizer_tr = Resizer(self, Resizer.TOP_RIGHT,
                                          self.__style.window.SHADOW_RADIUS_PX)
                self.resizer_br = Resizer(self, Resizer.BOTTOM_RIGHT,
                                          self.__style.window.SHADOW_RADIUS_PX)
                self.resizer_bl = Resizer(self, Resizer.BOTTOM_LEFT,
                                          self.__style.window.SHADOW_RADIUS_PX)

        def get_window_frame_widget(
                window_buttons_position: str) -> WindowFrame:
            """Returns a widget which acts as Qt 'windowFrame' element"""
            window_frame_widget = WindowFrame(
                titlebar_height=self.__titlebar_height,
                titlebar_color=self.__titlebar_color,
                background_color=self.__style.window.WINDOW_BACKGROUND_RGB,
                corner_radius=self.__style.window.WINDOW_CORNER_RADIUS_PX,
                parent=self)
            if not isinstance(window, QDIALOG_TYPES):
                window_frame_widget.double_clicked.connect(
                    self.__on_title_bar_double_clicked)  # type: ignore
            window_frame_widget.setObjectName('windowFrame')
            self.__vbox_frame_layout = QVBoxLayout(window_frame_widget)
            self.__vbox_frame_layout.setContentsMargins(0, 0, 0, 0)
            self.__vbox_frame_layout.setSpacing(0)
            if self.__title_bar:  # add optional titlebar
                tcolor = QColor(self.__style.window.TITLE_BAR_FONT_COLOR_RGB)
                if titlebar_text_color is not None:
                    tcolor = titlebar_text_color
                self.__title_widget = WindowTitleLabel(
                    text='',
                    height=self.__titlebar_height,
                    color=tcolor,
                    window_buttons_position=window_buttons_position,
                    margin=self.__style.window.
                    TITLE_BAR_TITLE_TEXT_RIGHT_MARGIN_PX,
                    button_bar_width=self.window_buttons_width +
                    self.window_buttons_margin,
                    minimum_width=self.__style.window.
                    TITLE_LABEL_MINIMUM_WIDTH_PX,
                    parent=None)
                self.__title_widget.setMinimumHeight(self.__titlebar_height)
                self.__vbox_frame_layout.addWidget(self.__title_widget)
            else:  # no title-bar; add dummy widget to create a margin
                self.__title_widget = QWidget()
                self.__title_widget.setGeometry(
                    0, 0, 1, self.__style.window.TITLE_BAR_TOP_MARGIN_PX)
                self.__title_widget.setMinimumHeight(
                    self.__style.window.TITLE_BAR_TOP_MARGIN_PX)
                self.__vbox_frame_layout.addWidget(self.__title_widget)
            assert self.__window is not None
            self.__vbox_frame_layout.addWidget(self.__window)
            return window_frame_widget

        QDialog.__init__(self, parent)
        self.__style: Any = style
        if titlebar_height is None:
            titlebar_height = self.__style.window.TITLE_BAR_HEIGHT_PX
        assert window_buttons_position in [
            None, WINDOW_BUTTONS_LEFT, WINDOW_BUTTONS_RIGHT
        ]
        if window_buttons_position is None:
            window_buttons_position = WINDOW_BUTTONS_LEFT if sys.platform == 'darwin' else WINDOW_BUTTONS_RIGHT
        self.__window: Optional[QWidget] = window
        self.__title_bar: bool = True if isinstance(
            self.__window, QDIALOG_TYPES) else title_bar
        self.__titlebar_height: int = titlebar_height
        self.__transparent_window: bool = transparent_window  # True if window uses WA_TranslucentBackground
        self.__use_shadow: bool = sys.platform != 'darwin' and transparent_window
        self.__maximized: bool = False
        self.__drag_move_enabled: bool = True
        self.__mouse_pressed: bool = False
        self.__mouse_pos: Optional[QPoint] = None
        self.__window_pos: Optional[QPoint] = None
        self.__window_buttons_width: int = 0
        self.__window_buttons_position: str = window_buttons_position
        # create main contaner layout
        self.__vbox_master_layout = QGridLayout(self)
        self.__vbox_master_layout.setContentsMargins(0, 0, 0, 0)
        if titlebar_color is None:
            self.__titlebar_color: QColor = QColor(
                self.__style.window.TITLE_BAR_COLOR_RGB)
        else:
            self.__titlebar_color = titlebar_color
        if titlebar_nofocus_color is None:
            self.__titlebar_nofocus_color: QColor = QColor(
                self.__style.window.TITLE_BAR_NOFOCUS_COLOR_RGB)
        else:
            self.__titlebar_nofocus_color = titlebar_nofocus_color
        self.__window_frame_widget: WindowFrame = get_window_frame_widget(
            window_buttons_position=self.__window_buttons_position)
        self.__vbox_master_layout.addWidget(self.__window_frame_widget, 0, 0)
        # run window initialization methods
        add_resizers()
        if isinstance(self.__window, QMessageBox):
            expose_msgbox_methods()
        adjust_wrapped_window_object()
        add_window_drop_shadow()
        add_window_buttons()
        set_window_properties(self.__titlebar_height)
        # connect slot to detect if window/app loses focus
        app = QApplication.instance()
        app.focusChanged.connect(self.__app_focus_changed_slot)
        self.setFocus()
        self.layout().setSizeConstraint(
            QLayout.SetMinimumSize
        )  # ensure widgets cannot be resized below their min size
        # attributes for title-bar tab widget
        self.__tab_widget_dummy_close_button: Optional[QWidget] = None
        self.__tab_widget_filter: Optional[TabFilter] = None
        self.__tab_widget: Optional[QTabWidget] = None
        if titlebar_widget is not None:
            self.__adjust_title_tabwidget(titlebar_widget)

    def keyPressEvent(self, event: QKeyEvent) -> None:
        """Ignore Escape key as it closes the window"""
        if event.key() != Qt.Key_Escape:
            super().keyPressEvent(event)

    # def keyPressEvent(self, e: QKeyPressEvent) -> None:
    #     """
    #     Example code for moving a window that has no titlebar between 2 screens with different
    #     scaling factors on Windows without any graphical artifacts.
    #     """
    #     scr = QApplication.instance().screens()
    #     if e.key() == Qt.Key_1:
    #         s = scr[0].availableSize()
    #         o = 0
    #     elif e.key() == Qt.Key_2:
    #         s = scr[1].availableSize()
    #         o = scr[1].availableSize().width()
    #     else:
    #         return
    #     size = self.size()
    #     pos = QPoint(s.width()/2 - self.width()/2 + o, s.height()/2 - self.height()/2)
    #     self.showMinimized()
    #     self.setGeometry(pos.x(), pos.y(), size.width(), size.height())
    #     self.showNormal()
    #     self.resize(size)

    def setWindowIcon(self, icon: Union[QIcon, QPixmap]) -> None:
        """Sets the window's window icon"""
        super().setWindowIcon(icon)
        if sys.platform == 'win32':  # make icon show up in taskbar on Windows
            import ctypes  # pylint: disable=import-outside-toplevel
            myappid: str = 'mycompany.qtmodern.redux.version'  # arbitrary string
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                myappid)

    # ############################################################
    # Properties
    # ############################################################

    def get_style(self) -> Any:
        """returns the class (static) which holds all the style's properties"""
        return self.__style

    @property
    def use_shadow(self) -> bool:
        """returns true if the window features a drop-shadow not generated by the OS or window manager"""
        return self.__use_shadow

    @property
    def maximized(self) -> bool:
        """Returns true if the window is maximzed"""
        return self.__maximized

    @property
    def titlebar_height(self) -> int:
        """Returns the title bar's height in pixels"""
        return self.__titlebar_height

    @property
    def window_buttons_width(self) -> int:
        """Returns the width of the area taken up by the titlebar buttons"""
        diameter: int = self.__style.window.TITLE_BAR_BUTTON_DIAMETER_PX
        spacing: int = self.__style.window.TITLE_BAR_BUTTON_SPACING_PX - diameter
        if not isinstance(self.__window, QDIALOG_TYPES):
            return spacing * 2 + diameter * 3
        return diameter

    @property
    def window_buttons_margin(self) -> int:
        """
        Returns the distance of the titlebar buttons from the left window edge.
        The maximum value is the distance of the buttons from the top edge, the minimum distance is defined in the
        window style. This value changes depending on the size of the window.
        """
        margin_y: int = int(
            (self.__titlebar_height -
             self.__style.window.TITLE_BAR_BUTTON_DIAMETER_PX) / 2)
        min_margin: int = round(
            self.__style.window.TITLE_BAR_BUTTON_MIN_X_MARGIN_PX if margin_y <
            self.__style.window.TITLE_BAR_BUTTON_MIN_X_MARGIN_PX else margin_y)
        return min_margin

    # ############################################################
    # Overloaded Qt methods
    # ############################################################

    def resizeEvent(self, r: QResizeEvent) -> None:
        """
        Qt Resize Event - does two things:
        1) applies rounded corners if window transparency has been disabled
        2) ensures resizer widgets and titlebar buttons stay in place
        TODO: the 'rounded_corner_px' value is harcoded. It relates to in '.TitleTabBar::tab' in
              widgetstyle/tabwidget_titlebar.py (border-width and border-image QSS attribute).
              Action: investigate a way so that this value doesn't have to be hard-coded.
        """
        # if transparency is turned off, set a mask for some basic rounded corners:
        if not self.__transparent_window and self.__style.window.WINDOW_CORNER_RADIUS_PX > 0:
            path = QPainterPath()
            path.addRoundedRect(
                QRectF(self.rect()),
                self.__style.window.WINDOW_CORNER_RADIUS_PX +
                1,  # add 1 to avoid drawing artifacts
                self.__style.window.WINDOW_CORNER_RADIUS_PX + 1)
            reg = QRegion(path.toFillPolygon().toPolygon())
            self.setMask(reg)
        # adjust window button positions
        if not isinstance(self.__window, QDIALOG_TYPES):
            self.resizer_bl.adjust_resizers(self.geometry(
            ))  # adjusting one resizer adjusts all other resizers too
        if self.__window_buttons_position == WINDOW_BUTTONS_RIGHT:
            self.__move_window_buttons()
        # if a titlebar tab widget is set, mask it so that the empty area can be used to drag the window
        if self.__tab_widget is not None:
            width = 0
            tab_bar = self.__tab_widget.tabBar()
            for i in range(0,
                           tab_bar.count() -
                           1):  # don't count invisible tab at end
                if tab_bar.isTabVisible(i):
                    width += tab_bar.tabRect(i).width()
            rounder_corner_px = 8  # TODO  # related to hardcoded border-image value in widgetstyle/tabwidget_titlebar.py
            r = QRect(0, 0, width + rounder_corner_px, self.__titlebar_height)
            self.__tab_widget.tabBar().setMask(r)

    def showEvent(self, _: QShowEvent) -> None:
        """
        Qt Show Event:
        * ensures resizers stay in place
        * ensures window is centered on screen or relative to their parent window
        """
        if not isinstance(self.__window, QDIALOG_TYPES):
            self.resizer_bl.adjust_resizers(self.geometry(
            ))  # adjusting one resizer adjusts all other resizers too
        parent = self.parent()
        # center MainWindow on screen
        if CENTER_MAINWINDOW:
            if parent is None:
                self.move(
                    QApplication.desktop().screenGeometry(self).center() -
                    self.rect().center())  # type: ignore
        # center dialogs relative to parent window
        if ALIGN_CHILD_WINDOWS and parent is not None:
            try:
                pos = parent.wrapper.pos(
                )  # parent is wrapped in a QtModernRedux window
                width = parent.wrapper.width()
                height = parent.wrapper.height()
            except AttributeError:
                pos = parent.pos()  # parent isn't wrapped
                width = parent.width()
                height = parent.height()
            x = pos.x() + (width / 2 - self.width() / 2)
            y = pos.y() + (height / 2 - self.height() / 2)
            self.move(x, y)

    def closeEvent(self, event: QCloseEvent) -> None:
        """Qt Close Event: Window close & cleanup"""
        if not self.__window:
            event.accept()
        else:
            self.__window.close()
            event.setAccepted(self.__window.isHidden())

    def eventFilter(self, target: QObject, event: QEvent) -> bool:
        """Qt Event Filter: This event filter is used when display QDialogs, such as message boxes."""
        # FOR MESSAGEBOX:
        if isinstance(event, QResizeEvent):
            assert self.__window is not None
            geometry = self.__window.geometry()
            if sys.platform in ['darwin']:
                self.setFixedSize(
                    geometry.width() + self.__style.window.SHADOW_RADIUS_PX *
                    2,  # macOS, Windows (?)
                    geometry.height() +
                    self.__style.window.SHADOW_RADIUS_PX * 2)
            else:
                self.setFixedSize(
                    geometry.width() +
                    self.__style.window.SHADOW_RADIUS_PX * 2,
                    geometry.height() +
                    self.__style.window.SHADOW_RADIUS_PX * 2 +
                    self.__titlebar_height)
            return True
        return QObject.eventFilter(self, target, event)

    def setIcon(self, icon: Any) -> None:
        """Qt setIcon: sets custom icons from the theme for QMessageBox"""
        if isinstance(self.__window,
                      QMessageBox) and icon != QMessageBox.NoIcon:
            icons = {
                QMessageBox.Information:
                self.__style.window.MSGBOX_ICON_INFORMATION,
                QMessageBox.Question: self.__style.window.MSGBOX_ICON_QUESTION,
                QMessageBox.Warning: self.__style.window.MSGBOX_ICON_WARNING,
                QMessageBox.Critical: self.__style.window.MSGBOX_ICON_CRITICAL,
            }
            self.__window.setIconPixmap(self.__load_svg(icons[icon]))

    def setWindowTitle(self, title: str) -> None:
        """Qt setWindowTitle: ensures the titlebar displays the window title"""
        super().setWindowTitle(title)
        if self.__title_bar:
            self.__title_widget.setWindowTitle(title)

    def setWindowFlag(self, flag: Any, on: bool = True) -> None:
        """Qt setWindowFlag"""
        button_hints = [
            Qt.WindowCloseButtonHint, Qt.WindowMinimizeButtonHint,
            Qt.WindowMaximizeButtonHint
        ]
        if flag in button_hints:
            self.__set_window_button_state(flag, on)
        else:
            QWidget.setWindowFlag(self, flag, on)

    def setWindowFlags(self, flags: Any) -> None:
        """Qt setWindowFlags"""
        button_hints = [
            Qt.WindowCloseButtonHint, Qt.WindowMinimizeButtonHint,
            Qt.WindowMaximizeButtonHint
        ]
        for hint in button_hints:
            self.__set_window_button_state(hint, bool(flags & hint))
        QWidget.setWindowFlags(self, flags)

    def mousePressEvent(self, event: QMouseEvent) -> None:
        """Qt Mouse-Press Event: Window dragging"""
        if not self.__drag_move_enabled:
            return
        radius = self.__style.window.SHADOW_RADIUS_PX if self.__use_shadow else 0
        ep = event.pos()
        if radius <= ep.y() <= self.__titlebar_height + radius + 1:
            epx = ep.x()
            if radius < epx < self.width() - radius:
                self.__mouse_pressed = True
                self.__mouse_pos = event.globalPos()
                self.__window_pos = self.pos()
                # self.__os = self.size()  # use when dragging between screens with different scale factors

    def mouseMoveEvent(self, event: QMouseEvent) -> None:
        """Qt Mouse-Move Event: Window dragging"""
        if self.__mouse_pressed and self.__drag_move_enabled:
            # CODE TO DETECT DISPLAY CHANGE
            # display = self.screen().virtualSiblingAt(event.globalPos()).name()
            # if display != self.__old_display:
            #     print('DISPLAY CHANGED TO %s' % display, self.__old_size)
            #     self.__old_display = display
            self.move(self.__window_pos +
                      (event.globalPos() - self.__mouse_pos))

    def mouseReleaseEvent(self, _: QMouseEvent) -> None:
        """Qt Mouse-Release Event: Window dragging"""
        self.__mouse_pressed = False
        # self.resize(self.__os)  # use when dragging between screens with different scale factors

    # ############################################################
    # PRIVATE Custom methods
    # ############################################################

    def __move_window_buttons(self) -> None:
        """
        MacOS:
            * buttons are on the right side of the titlebar
            * order (left to right): close, minimize/restore, maximize
        Windows and most Linux:
            * buttons are on the left side of the titlebar
            * order (left to right): minimize/restore, maximize, close
        """
        # determine shadow radius
        radius: int = self.__style.window.SHADOW_RADIUS_PX
        if self.__use_shadow is False or self.__maximized:
            radius = 0
        # determine combined button width
        diameter = self.__style.window.TITLE_BAR_BUTTON_DIAMETER_PX
        spacing = self.__style.window.TITLE_BAR_BUTTON_SPACING_PX - diameter
        buttons_width: int = self.window_buttons_width
        # determine individual button positions
        margin_y: int = int((self.__titlebar_height - diameter) / 2)
        min_margin = self.window_buttons_margin
        if self.__window_buttons_position == WINDOW_BUTTONS_RIGHT:
            ofs_min = 0
            ofs_max = diameter + spacing
            if not isinstance(self.__window, QDIALOG_TYPES):
                ofs_close = (diameter + spacing) * 2
            else:
                ofs_close = 0
            margin_x: int = self.width() - buttons_width - min_margin - radius
        else:
            ofs_close = 0
            ofs_min = diameter + spacing
            ofs_max = (diameter + spacing) * 2
            margin_x = min_margin + radius
        # move buttons
        self._close_button.move(margin_x + ofs_close, margin_y + radius)
        if not isinstance(self.__window, QDIALOG_TYPES
                          ):  # no additional window buttons for message-boxes
            self._minimize_button.move(margin_x + ofs_min, margin_y + radius)
            self._restore_button.move(margin_x + ofs_max, margin_y + radius)
            self._maximize_button.move(margin_x + ofs_max, margin_y + radius)

    def __load_svg(self, svg_file: str) -> QPixmap:
        """Loads a SVG file and scales it correctly for High-DPI screens"""
        svg_renderer = QSvgRenderer(svg_file)
        pixmap = QPixmap(svg_renderer.defaultSize() * self.devicePixelRatio())
        pixmap.fill(Qt.transparent)
        painter = QPainter()
        painter.begin(pixmap)
        svg_renderer.render(painter)
        painter.end()
        pixmap.setDevicePixelRatio(self.devicePixelRatio())
        return pixmap

    def __show_resizers(self, show: bool) -> None:
        """Shows / hides the resizer widgets"""
        self.resizer_top.setVisible(show)
        self.resizer_bot.setVisible(show)
        self.resizer_lef.setVisible(show)
        self.resizer_rig.setVisible(show)
        self.resizer_tl.setVisible(show)
        self.resizer_tr.setVisible(show)
        self.resizer_bl.setVisible(show)
        self.resizer_br.setVisible(show)

    def __child_was_closed(self) -> None:
        """Wrapped window was closed"""
        self.__window = None  # The child was deleted, remove the reference to it and close the parent window
        self.close()

    def __set_window_button_state(self, hint: Any, state: Any) -> None:  # pylint: disable=too-many-branches
        """Adjusts button state (enabled/disabled) based on window status"""
        buttons = {Qt.WindowCloseButtonHint: self._close_button}
        if not isinstance(self.__window, QDIALOG_TYPES):
            buttons[Qt.WindowMinimizeButtonHint] = self._minimize_button
            buttons[Qt.WindowMaximizeButtonHint] = self._maximize_button
        button = buttons.get(hint)
        maximized = bool(self.windowState() & Qt.WindowMaximized)
        if not isinstance(self.__window, QDIALOG_TYPES):
            if button == self._maximize_button:  # special rules for max/restore
                self._restore_button.setEnabled(state)
                self._maximize_button.setEnabled(state)
                if maximized:
                    self._restore_button.setVisible(state)
                    self._maximize_button.setVisible(False)
                else:
                    self._maximize_button.setVisible(state)
                    self._restore_button.setVisible(False)
            elif button is not None:
                button.setEnabled(state)
        elif button is not None:
            button.setEnabled(state)
        all_buttons = [self._close_button]
        if not isinstance(self.__window, QDIALOG_TYPES):
            all_buttons.append(self._minimize_button)
            all_buttons.append(self._maximize_button)
            all_buttons.append(self._restore_button)
        if True in [button.isEnabled() for button in all_buttons]:
            for button in all_buttons:
                button.setVisible(True)
            if not isinstance(self.__window, QDIALOG_TYPES):
                if maximized:
                    self._maximize_button.setVisible(False)
                else:
                    self._restore_button.setVisible(False)
        else:
            for button in all_buttons:
                button.setVisible(False)

    @Slot()  # type: ignore
    def __on_minimize_button_clicked(self) -> None:
        """
        macOS workaround: Frameless windows cannot be minimized on macOS, therefore we need
        to reinstate the titlebar before we minimize the window. Once the window minimize
        command has been issued, we can hide the titlebar again.
        """
        if sys.platform == 'darwin':
            self.setWindowFlags(Qt.Window | Qt.WindowSystemMenuHint
                                | Qt.WindowCloseButtonHint
                                | Qt.WindowMinimizeButtonHint
                                | Qt.WindowMaximizeButtonHint)
            self.show()
        self.setWindowState(Qt.WindowMinimized)
        self._minimize_button.setAttribute(
            Qt.WA_UnderMouse, False)  # prevent hover state form getting stuck
        if sys.platform == 'darwin':
            self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint
                                | Qt.WindowSystemMenuHint
                                | Qt.WindowCloseButtonHint
                                | Qt.WindowMinimizeButtonHint
                                | Qt.WindowMaximizeButtonHint)
            self.show()

    @Slot()  # type: ignore
    def __on_restore_button_clicked(self) -> None:
        """Restore button clicked"""
        if self._maximize_button.isEnabled() or self._restore_button.isEnabled(
        ):
            self._restore_button.setVisible(False)
            self._restore_button.setEnabled(False)
            self._maximize_button.setVisible(True)
            self._maximize_button.setEnabled(True)
        self.__maximized = False
        if self.__use_shadow:
            self.layout().setMargin(self.__style.window.SHADOW_RADIUS_PX
                                    )  # adjust window for drop-shadow margin
            self.__show_resizers(True)
            self.__drag_move_enabled = True
        self.__move_window_buttons()  # adjust window for drop-shadow margin
        self.setWindowState(Qt.WindowNoState)
        self._maximize_button.setAttribute(
            Qt.WA_UnderMouse, False)  # prevent hover state form getting stuck
        self._restore_button.setAttribute(Qt.WA_UnderMouse, False)

    @Slot()  # type: ignore
    def __on_maximize_button_clicked(self) -> None:
        """Maximize button clicked"""
        if self._maximize_button.isEnabled() or self._restore_button.isEnabled(
        ):
            self._restore_button.setVisible(True)
            self._restore_button.setEnabled(True)
            self._maximize_button.setVisible(False)
            self._maximize_button.setEnabled(False)
        self.__maximized = True
        if self.__use_shadow:
            self.layout().setMargin(0)
            self.__show_resizers(False)
            self.__drag_move_enabled = False
        self.__move_window_buttons()  # adjust window for drop-shadow margin
        self.setWindowState(
            Qt.WindowMaximized)  # adjust window for drop-shadow margin
        self._maximize_button.setAttribute(
            Qt.WA_UnderMouse, False)  # prevent hover state form getting stuck
        self._restore_button.setAttribute(Qt.WA_UnderMouse, False)

    @Slot()  # type: ignore
    def __on_close_button_clicked(self) -> None:
        """Close button clicked"""
        self.close()

    @Slot()  # type: ignore
    def __on_title_bar_double_clicked(self) -> None:
        """Maximize / Restore window on titlebar double click"""
        if not bool(self.windowState() & Qt.WindowMaximized):
            self.__on_maximize_button_clicked()
        else:
            self.__on_restore_button_clicked()

    def __get_title_tabwidget_style(self, background: QColor) -> str:
        """Gets the QSS for the TabBar widget and adjusts margins"""
        margin_top_px = self.__style.window.TITLE_BAR_TOP_MARGIN_PX
        top_bottom_border_px = (
            self.__style.window.TITLE_BAR_TAB_CSS_TOP_BORDER_PX +
            self.__style.window.TITLE_BAR_TAB_CSS_BOTTOM_BORDER_PX)
        height_px = self.titlebar_height - margin_top_px - top_bottom_border_px
        style: str = self.__style.get_title_tabwidget_stylesheet()
        style = style.replace('{TITLEBAR_HEIGHT}', str(height_px))
        button_margin = str(
            self.window_buttons_margin + self.window_buttons_width +
            self.__style.window.TITLE_BAR_BUTTON_MIN_X_MARGIN_PX)
        if self.__window_buttons_position == WINDOW_BUTTONS_RIGHT:
            style = style.replace('{WINDOW_BUTTON_MARGIN_LEFT}', str(0))
            style = style.replace('{WINDOW_BUTTON_MARGIN_RIGHT}',
                                  button_margin)
        else:
            style = style.replace('{WINDOW_BUTTON_MARGIN_LEFT}', button_margin)
            style = style.replace('{WINDOW_BUTTON_MARGIN_RIGHT}', str(0))
        style = style.replace('{BACKGROUND_COLOR}', background.name())
        return style

    @Slot()  # type: ignore
    def __app_focus_changed_slot(self) -> None:
        """Changes the titlebar's background color when the window acquires/loses focus"""
        if self.isActiveWindow():
            self.__window_frame_widget.set_background_color(
                self.__titlebar_color)
        else:
            self.__window_frame_widget.set_background_color(
                self.__titlebar_nofocus_color)
        self.__window_frame_widget.update()

        if self.__tab_widget is not None:
            if self.isActiveWindow():
                self.__tab_widget.setStyleSheet(
                    self.__get_title_tabwidget_style(self.__titlebar_color))
            else:
                self.__tab_widget.setStyleSheet(
                    self.__get_title_tabwidget_style(
                        self.__titlebar_nofocus_color))

    def __adjust_title_tabwidget(self, tab_widget: QTabWidget) -> None:
        """
        Adjusts a TabWidget to work as a Google Chrome style tab bar in the window's title bar.
        TODO: to properly center the Widget (doesn't apply to TabWidgets) a bottom margin of 2px is needed;
              why is that? Action: track down source for this offset in the QSS definitions and figure out the
              proper calculation of this value.
        """
        # add properties to ensure style is only applied tabwidget in titlebar
        if isinstance(tab_widget, QTabWidget):
            self.__tab_widget = tab_widget
            tab_widget.setProperty("class", "TitleTabWidget")  # type: ignore
            tab_widget.tabBar().setProperty("class",
                                            "TitleTabBar")  # type: ignore
            tab_widget.setStyleSheet(
                self.__get_title_tabwidget_style(self.__titlebar_color))
            # insert empty disabled tab for rounded effect on last tab
            idx = tab_widget.addTab(QWidget(), "")
            tab_widget.setTabEnabled(idx, False)
            self.__tab_widget_dummy_close_button = QWidget()
            self.__tab_widget_dummy_close_button.setGeometry(0, 0, 1, 1)
            # remove close button for last widget
            tab_widget.tabBar().setTabButton(
                idx, QTabBar.RightSide, self.__tab_widget_dummy_close_button)
            self.__tab_widget_filter = TabFilter(tab_widget.tabBar())
            tab_widget.tabBar().installEventFilter(self.__tab_widget_filter)
            tab_widget.tabBar().setDocumentMode(True)
            tab_widget.tabBar().setExpanding(False)
        else:
            margin_top_px: int = self.__style.window.TITLE_BAR_TOP_MARGIN_PX  # type: ignore
            margin_bottom_px: int = self.__style.window.TITLE_BAR_TAB_CSS_BOTTOM_BORDER_PX
            height_px: int = self.titlebar_height - margin_top_px - margin_bottom_px
            button_margin: int = (
                self.window_buttons_margin + self.window_buttons_width +
                self.__style.window.TITLE_BAR_BUTTON_MIN_X_MARGIN_PX)
            non_button_margin: int = self.__style.window.TITLE_BAR_BUTTON_MIN_X_MARGIN_PX
            if self.__window_buttons_position == WINDOW_BUTTONS_RIGHT:
                tab_widget.layout().setContentsMargins(non_button_margin, 2,
                                                       button_margin,
                                                       0)  # TODO: why 2?
            else:
                tab_widget.layout().setContentsMargins(button_margin, 2,
                                                       non_button_margin,
                                                       0)  # TODO: why 2?
            tab_widget.setMaximumHeight(height_px)
            tab_widget.setMinimumHeight(height_px)
Example #21
0
    def __init__(self, identifier, area, menu=None, parent=None):
        """

        Args:
            identifier (str)
            area (str): either "rows", "columns", or "frozen"
            menu (FilterMenu, optional)
            parent (QWidget, optional): Parent widget
        """
        super().__init__(parent=parent)
        self._identifier = identifier
        self._area = area
        layout = QHBoxLayout(self)
        button = QToolButton(self)
        button.setPopupMode(QToolButton.InstantPopup)
        button.setStyleSheet("QToolButton {border: none;}")
        button.setEnabled(menu is not None)
        if menu:
            self.menu = menu
            button.setMenu(self.menu)
            self.menu.anchor = self
        self.drag_start_pos = None
        label = QLabel(identifier)
        layout.addWidget(label)
        layout.addWidget(button)
        layout.setContentsMargins(self._H_MARGIN, 0, self._H_MARGIN, 0)
        if area == "rows":
            h_alignment = Qt.AlignLeft
            layout.insertSpacing(1, self._SPACING)
            button.setArrowType(Qt.DownArrow)
        elif area == "columns":
            h_alignment = Qt.AlignRight
            layout.insertSpacing(0, self._SPACING)
            button.setArrowType(Qt.RightArrow)
        elif area == "frozen":
            h_alignment = Qt.AlignHCenter
        label.setAlignment(h_alignment | Qt.AlignVCenter)
        label.setStyleSheet("QLabel {font-weight: bold;}")
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setAutoFillBackground(True)
        self.setFrameStyle(QFrame.Raised)
        self.setFrameShape(QFrame.Panel)
        self.setStyleSheet("QFrame {background: " + PIVOT_TABLE_HEADER_COLOR +
                           ";}")
        self.setAcceptDrops(True)
        self.setToolTip(
            "<p>This is a draggable header. </p>"
            "<p>Drag-and-drop it onto another header to pivot the table, "
            "or onto the Frozen table to freeze this dimension.</p>")
        self.adjustSize()
        self.setMinimumWidth(self.size().width())
Example #22
0
    def __init__(self,
                 original,
                 processed,
                 title=None,
                 parent=None,
                 export=False):
        super(ImageViewer, self).__init__(parent)
        if original is None and processed is None:
            raise ValueError(
                self.tr("ImageViewer.__init__: Empty image received"))
        if original is None and processed is not None:
            original = processed
        self.original = original
        self.processed = processed
        if self.original is not None and self.processed is None:
            self.view = DynamicView(self.original)
        else:
            self.view = DynamicView(self.processed)

        # view_label = QLabel(self.tr('View:'))
        self.original_radio = QRadioButton(self.tr("Original"))
        self.original_radio.setToolTip(
            self.
            tr("Show the original image for comparison (press SPACE to toggle)"
               ))
        self.process_radio = QRadioButton(self.tr("Processed"))
        self.process_radio.setToolTip(
            self.tr(
                "Show result of the current processing (press SPACE to toggle)"
            ))
        self.zoom_label = QLabel()
        full_button = QToolButton()
        full_button.setText(self.tr("100%"))
        fit_button = QToolButton()
        fit_button.setText(self.tr("Fit"))
        height, width, _ = self.original.shape
        size_label = QLabel(self.tr(f"[{height}x{width} px]"))
        export_button = QToolButton()
        export_button.setToolTip(self.tr("Export processed image"))
        # export_button.setText(self.tr('Export...'))
        export_button.setIcon(QIcon("icons/export.svg"))

        tool_layout = QHBoxLayout()
        tool_layout.addWidget(QLabel(self.tr("Zoom:")))
        tool_layout.addWidget(self.zoom_label)
        # tool_layout.addWidget(full_button)
        # tool_layout.addWidget(fit_button)
        tool_layout.addStretch()
        if processed is not None:
            # tool_layout.addWidget(view_label)
            tool_layout.addWidget(self.original_radio)
            tool_layout.addWidget(self.process_radio)
            tool_layout.addStretch()
        tool_layout.addWidget(size_label)
        if export or processed is not None:
            tool_layout.addWidget(export_button)
        if processed is not None:
            self.original_radio.setChecked(False)
            self.process_radio.setChecked(True)
            self.toggle_mode(False)

        vert_layout = QVBoxLayout()
        if title is not None:
            self.title_label = QLabel(title)
            modify_font(self.title_label, bold=True)
            self.title_label.setAlignment(Qt.AlignCenter)
            vert_layout.addWidget(self.title_label)
        else:
            self.title_label = None
        vert_layout.addWidget(self.view)
        vert_layout.addLayout(tool_layout)
        self.setLayout(vert_layout)

        self.original_radio.toggled.connect(self.toggle_mode)
        fit_button.clicked.connect(self.view.zoom_fit)
        full_button.clicked.connect(self.view.zoom_full)
        export_button.clicked.connect(self.export_image)
        self.view.viewChanged.connect(self.forward_changed)
Example #23
0
    def __init__(self, camera):
        super(camera_view, self).__init__()
        self.camera = camera
        self.image_camera = np.zeros((1,1))
        #self.image = QImage()

        self.button_up = QToolButton()
        self.button_up.setArrowType(Qt.UpArrow)
        self.button_down = QToolButton()
        self.button_down.setArrowType(Qt.DownArrow)
        self.button_left = QToolButton()
        self.button_left.setArrowType(Qt.LeftArrow)
        self.button_right = QToolButton()
        self.button_right.setArrowType(Qt.RightArrow)

        self.box_button = QVBoxLayout()
        self.box_button_centre = QHBoxLayout()
        self.box_button_centre.addWidget(self.button_left)
        self.box_button_centre.addWidget(self.button_right)
        self.box_button_centre.addStretch(1)
        self.box_button.addWidget(self.button_up)
        self.box_button.addLayout(self.box_button_centre)
        self.box_button.addWidget(self.button_down)
        self.box_button.addStretch(1)

        self.button_affichage = QPushButton("Affichage Vidéo")
        self.button_affichage.clicked.connect(self.Affichage)

        self.button_stop = QPushButton("Stop retour Vidéo")
        self.button_stop.clicked.connect(self.fin_video)
        self.box = QVBoxLayout()
        self.box.addWidget(self.button_affichage)
        self.box.addWidget(self.button_stop)

        self.box_panel = QHBoxLayout()
        self.box_panel.addLayout(self.box)
        self.box_panel.addLayout(self.box_button)

        self.image2 = QLabel()
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.image2)

        self.layout.addLayout(self.box_panel)
        self.setLayout(self.layout)


        self.movie_thread = MovieThread(self.camera)
        self.movie_thread.changePixmap.connect(self.setImage)
        self.movie_thread.setTerminationEnabled(True)

        #
        self.button_up.clicked.connect(self.up)
        self.button_down.clicked.connect(self.down)
        self.button_right.clicked.connect(self.right)
        self.button_left.clicked.connect(self.left)

        #box enregistrement + photo
        self.button_debut_video = QPushButton("Début enregistrement Vidéo")
        self.button_fin_video = QPushButton("Fin enregistrement Vidéo")
        self.button_photo = QPushButton("Photo")
        self.box_video_photo = QVBoxLayout()
        self.box_video_photo.addWidget(self.button_debut_video)
        self.box_video_photo.addWidget(self.button_fin_video)
        self.box_video_photo.addWidget(self.button_photo)
        self.box_panel.addLayout(self.box_video_photo)

        self.enregistrement = Record(self.camera)
        self.enregistrement.setTerminationEnabled(True)

        self.button_debut_video.clicked.connect(self.video_debut)
        self.button_fin_video.clicked.connect(self.video_fin)
        self.button_photo.clicked.connect(self.photo)
        self.compteur_photo = 0


        self.enregistrement.start()
        self.timer_enregistrement = QTimer()
        self.timer_enregistrement.setInterval((1/24)*1000)
        self.timer_enregistrement.timeout.connect(self.video)
Example #24
0
    def __init__(self, image, parent=None):
        super(CloningWidget, self).__init__(parent)

        self.detector_combo = QComboBox()
        self.detector_combo.addItems(
            [self.tr('BRISK'),
             self.tr('ORB'),
             self.tr('AKAZE')])
        self.detector_combo.setCurrentIndex(0)
        self.detector_combo.setToolTip(
            self.tr('Algorithm used for localization and description'))
        self.response_spin = QSpinBox()
        self.response_spin.setRange(0, 100)
        self.response_spin.setSuffix(self.tr('%'))
        self.response_spin.setValue(90)
        self.response_spin.setToolTip(
            self.tr('Maximum keypoint response to perform matching'))
        self.matching_spin = QSpinBox()
        self.matching_spin.setRange(1, 100)
        self.matching_spin.setSuffix(self.tr('%'))
        self.matching_spin.setValue(20)
        self.matching_spin.setToolTip(
            self.tr('Maximum metric difference to accept matching'))
        self.distance_spin = QSpinBox()
        self.distance_spin.setRange(1, 100)
        self.distance_spin.setSuffix(self.tr('%'))
        self.distance_spin.setValue(15)
        self.distance_spin.setToolTip(
            self.tr('Maximum distance between matches in the same cluster'))
        self.cluster_spin = QSpinBox()
        self.cluster_spin.setRange(1, 20)
        self.cluster_spin.setValue(5)
        self.cluster_spin.setToolTip(
            self.tr('Minimum number of keypoints to create a new cluster'))
        self.kpts_check = QCheckBox(self.tr('Show keypoints'))
        self.kpts_check.setToolTip(self.tr('Show keypoint coverage'))
        self.nolines_check = QCheckBox(self.tr('Hide lines'))
        self.nolines_check.setToolTip(self.tr('Disable match line drawing'))
        self.process_button = QToolButton()
        self.process_button.setText(self.tr('Process'))
        self.process_button.setToolTip(self.tr('Perform automatic detection'))
        modify_font(self.process_button, bold=True)
        self.status_label = QLabel()
        self.mask_label = QLabel()
        self.mask_button = QToolButton()
        self.mask_button.setText(self.tr('Load mask...'))
        self.mask_button.setToolTip(
            self.tr('Load an image to be used as mask'))
        self.onoff_button = QToolButton()
        self.onoff_button.setText(self.tr('OFF'))
        self.onoff_button.setCheckable(True)
        self.onoff_button.setToolTip(self.tr('Toggle keypoint detection mask'))

        self.image = image
        self.viewer = ImageViewer(self.image, self.image)
        self.gray = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY)
        self.total = self.kpts = self.desc = self.matches = self.clusters = self.mask = None
        self.canceled = False

        self.detector_combo.currentIndexChanged.connect(self.update_detector)
        self.response_spin.valueChanged.connect(self.update_detector)
        self.matching_spin.valueChanged.connect(self.update_matching)
        self.distance_spin.valueChanged.connect(self.update_cluster)
        self.cluster_spin.valueChanged.connect(self.update_cluster)
        self.nolines_check.stateChanged.connect(self.process)
        self.kpts_check.stateChanged.connect(self.process)
        self.process_button.clicked.connect(self.process)
        self.mask_button.clicked.connect(self.load_mask)
        self.onoff_button.toggled.connect(self.toggle_mask)
        self.onoff_button.setEnabled(False)

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr('Detector:')))
        top_layout.addWidget(self.detector_combo)
        top_layout.addWidget(QLabel(self.tr('Response:')))
        top_layout.addWidget(self.response_spin)
        top_layout.addWidget(QLabel(self.tr('Matching:')))
        top_layout.addWidget(self.matching_spin)
        top_layout.addWidget(QLabel(self.tr('Distance:')))
        top_layout.addWidget(self.distance_spin)
        top_layout.addWidget(QLabel(self.tr('Cluster:')))
        top_layout.addWidget(self.cluster_spin)
        top_layout.addWidget(self.nolines_check)
        top_layout.addWidget(self.kpts_check)
        top_layout.addStretch()

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.process_button)
        bottom_layout.addWidget(self.status_label)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.mask_button)
        bottom_layout.addWidget(self.onoff_button)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addLayout(bottom_layout)
        main_layout.addWidget(self.viewer)
        self.setLayout(main_layout)
class AddOrManageObjectGroupDialog(QDialog):
    def __init__(self, parent, object_class_item, db_mngr, *db_maps):
        """
        Args:
            parent (SpineDBEditor): data store widget
            object_class_item (ObjectClassItem)
            db_mngr (SpineDBManager)
            *db_maps: database mappings
        """
        super().__init__(parent)
        self.object_class_item = object_class_item
        self.db_mngr = db_mngr
        self.db_maps = db_maps
        self.db_map = db_maps[0]
        self.db_maps_by_codename = {
            db_map.codename: db_map
            for db_map in db_maps
        }
        self.db_combo_box = QComboBox(self)
        self.header_widget = QWidget(self)
        self.group_name_line_edit = QLineEdit(self)
        header_layout = QHBoxLayout(self.header_widget)
        header_layout.addWidget(QLabel(f"Group name: "))
        header_layout.addWidget(self.group_name_line_edit)
        header_layout.addSpacing(32)
        header_layout.addWidget(QLabel("Database"))
        header_layout.addWidget(self.db_combo_box)
        self.non_members_tree = QTreeWidget(self)
        self.non_members_tree.setHeaderLabel("Non members")
        self.non_members_tree.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.non_members_tree.setColumnCount(1)
        self.non_members_tree.setIndentation(0)
        self.members_tree = QTreeWidget(self)
        self.members_tree.setHeaderLabel("Members")
        self.members_tree.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.members_tree.setColumnCount(1)
        self.members_tree.setIndentation(0)
        self.add_button = QToolButton()
        self.add_button.setToolTip("<p>Add selected non-members.</p>")
        self.add_button.setIcon(QIcon(":/icons/menu_icons/cube_plus.svg"))
        self.add_button.setIconSize(QSize(24, 24))
        self.add_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.add_button.setText(">>")
        self.remove_button = QToolButton()
        self.remove_button.setToolTip("<p>Remove selected members.</p>")
        self.remove_button.setIcon(QIcon(":/icons/menu_icons/cube_minus.svg"))
        self.remove_button.setIconSize(QSize(24, 24))
        self.remove_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.remove_button.setText("<<")
        self.vertical_button_widget = QWidget()
        vertical_button_layout = QVBoxLayout(self.vertical_button_widget)
        vertical_button_layout.addStretch()
        vertical_button_layout.addWidget(self.add_button)
        vertical_button_layout.addWidget(self.remove_button)
        vertical_button_layout.addStretch()
        self.button_box = QDialogButtonBox(self)
        self.button_box.setStandardButtons(QDialogButtonBox.Cancel
                                           | QDialogButtonBox.Ok)
        layout = QGridLayout(self)
        layout.addWidget(self.header_widget, 0, 0, 1, 3, Qt.AlignHCenter)
        layout.addWidget(self.non_members_tree, 1, 0)
        layout.addWidget(self.vertical_button_widget, 1, 1)
        layout.addWidget(self.members_tree, 1, 2)
        layout.addWidget(self.button_box, 2, 0, 1, 3)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.db_combo_box.addItems(list(self.db_maps_by_codename))
        self.db_map_object_ids = {
            db_map: {
                x["name"]: x["id"]
                for x in self.db_mngr.get_items_by_field(
                    self.db_map, "object", "class_id",
                    self.object_class_item.db_map_id(db_map))
            }
            for db_map in db_maps
        }
        self.reset_list_widgets(db_maps[0].codename)
        self.connect_signals()

    def connect_signals(self):
        """Connect signals to slots."""
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.db_combo_box.currentTextChanged.connect(self.reset_list_widgets)
        self.add_button.clicked.connect(self.add_members)
        self.remove_button.clicked.connect(self.remove_members)

    def reset_list_widgets(self, database):
        self.db_map = self.db_maps_by_codename[database]
        object_ids = self.db_map_object_ids[self.db_map]
        members = []
        non_members = []
        for obj_name, obj_id in object_ids.items():
            if obj_id in self.initial_member_ids():
                members.append(obj_name)
            elif obj_id != self.initial_entity_id():
                non_members.append(obj_name)
        member_items = [QTreeWidgetItem([obj_name]) for obj_name in members]
        non_member_items = [
            QTreeWidgetItem([obj_name]) for obj_name in non_members
        ]
        self.members_tree.addTopLevelItems(member_items)
        self.non_members_tree.addTopLevelItems(non_member_items)

    def initial_member_ids(self):
        raise NotImplementedError()

    def initial_entity_id(self):
        raise NotImplementedError()

    @Slot(bool)
    def add_members(self, checked=False):
        indexes = sorted([
            self.non_members_tree.indexOfTopLevelItem(x)
            for x in self.non_members_tree.selectedItems()
        ],
                         reverse=True)
        items = [
            self.non_members_tree.takeTopLevelItem(ind) for ind in indexes
        ]
        self.members_tree.addTopLevelItems(items)

    @Slot(bool)
    def remove_members(self, checked=False):
        indexes = sorted([
            self.members_tree.indexOfTopLevelItem(x)
            for x in self.members_tree.selectedItems()
        ],
                         reverse=True)
        items = [self.members_tree.takeTopLevelItem(ind) for ind in indexes]
        self.non_members_tree.addTopLevelItems(items)

    def _check_validity(self):
        if not self.members_tree.topLevelItemCount():
            self.parent().msg_error.emit(
                "Please select at least one member object.")
            return False
        return True
Example #26
0
def _toggle_box_visibility(toggle_button: QToolButton, box: QGroupBox):
    box.setVisible(not box.isVisible())
    toggle_button.setText("-" if box.isVisible() else "+")
Example #27
0
class CloningWidget(ToolWidget):
    def __init__(self, image, parent=None):
        super(CloningWidget, self).__init__(parent)

        self.detector_combo = QComboBox()
        self.detector_combo.addItems(
            [self.tr('BRISK'),
             self.tr('ORB'),
             self.tr('AKAZE')])
        self.detector_combo.setCurrentIndex(0)
        self.detector_combo.setToolTip(
            self.tr('Algorithm used for localization and description'))
        self.response_spin = QSpinBox()
        self.response_spin.setRange(0, 100)
        self.response_spin.setSuffix(self.tr('%'))
        self.response_spin.setValue(90)
        self.response_spin.setToolTip(
            self.tr('Maximum keypoint response to perform matching'))
        self.matching_spin = QSpinBox()
        self.matching_spin.setRange(1, 100)
        self.matching_spin.setSuffix(self.tr('%'))
        self.matching_spin.setValue(20)
        self.matching_spin.setToolTip(
            self.tr('Maximum metric difference to accept matching'))
        self.distance_spin = QSpinBox()
        self.distance_spin.setRange(1, 100)
        self.distance_spin.setSuffix(self.tr('%'))
        self.distance_spin.setValue(15)
        self.distance_spin.setToolTip(
            self.tr('Maximum distance between matches in the same cluster'))
        self.cluster_spin = QSpinBox()
        self.cluster_spin.setRange(1, 20)
        self.cluster_spin.setValue(5)
        self.cluster_spin.setToolTip(
            self.tr('Minimum number of keypoints to create a new cluster'))
        self.kpts_check = QCheckBox(self.tr('Show keypoints'))
        self.kpts_check.setToolTip(self.tr('Show keypoint coverage'))
        self.nolines_check = QCheckBox(self.tr('Hide lines'))
        self.nolines_check.setToolTip(self.tr('Disable match line drawing'))
        self.process_button = QToolButton()
        self.process_button.setText(self.tr('Process'))
        self.process_button.setToolTip(self.tr('Perform automatic detection'))
        modify_font(self.process_button, bold=True)
        self.status_label = QLabel()
        self.mask_label = QLabel()
        self.mask_button = QToolButton()
        self.mask_button.setText(self.tr('Load mask...'))
        self.mask_button.setToolTip(
            self.tr('Load an image to be used as mask'))
        self.onoff_button = QToolButton()
        self.onoff_button.setText(self.tr('OFF'))
        self.onoff_button.setCheckable(True)
        self.onoff_button.setToolTip(self.tr('Toggle keypoint detection mask'))

        self.image = image
        self.viewer = ImageViewer(self.image, self.image)
        self.gray = cv.cvtColor(self.image, cv.COLOR_BGR2GRAY)
        self.total = self.kpts = self.desc = self.matches = self.clusters = self.mask = None
        self.canceled = False

        self.detector_combo.currentIndexChanged.connect(self.update_detector)
        self.response_spin.valueChanged.connect(self.update_detector)
        self.matching_spin.valueChanged.connect(self.update_matching)
        self.distance_spin.valueChanged.connect(self.update_cluster)
        self.cluster_spin.valueChanged.connect(self.update_cluster)
        self.nolines_check.stateChanged.connect(self.process)
        self.kpts_check.stateChanged.connect(self.process)
        self.process_button.clicked.connect(self.process)
        self.mask_button.clicked.connect(self.load_mask)
        self.onoff_button.toggled.connect(self.toggle_mask)
        self.onoff_button.setEnabled(False)

        top_layout = QHBoxLayout()
        top_layout.addWidget(QLabel(self.tr('Detector:')))
        top_layout.addWidget(self.detector_combo)
        top_layout.addWidget(QLabel(self.tr('Response:')))
        top_layout.addWidget(self.response_spin)
        top_layout.addWidget(QLabel(self.tr('Matching:')))
        top_layout.addWidget(self.matching_spin)
        top_layout.addWidget(QLabel(self.tr('Distance:')))
        top_layout.addWidget(self.distance_spin)
        top_layout.addWidget(QLabel(self.tr('Cluster:')))
        top_layout.addWidget(self.cluster_spin)
        top_layout.addWidget(self.nolines_check)
        top_layout.addWidget(self.kpts_check)
        top_layout.addStretch()

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.process_button)
        bottom_layout.addWidget(self.status_label)
        bottom_layout.addStretch()
        bottom_layout.addWidget(self.mask_button)
        bottom_layout.addWidget(self.onoff_button)

        main_layout = QVBoxLayout()
        main_layout.addLayout(top_layout)
        main_layout.addLayout(bottom_layout)
        main_layout.addWidget(self.viewer)
        self.setLayout(main_layout)

    def toggle_mask(self, checked):
        self.onoff_button.setText('ON' if checked else 'OFF')
        if checked:
            self.viewer.update_processed(
                cv.merge([c * self.mask for c in cv.split(self.image)]))
        else:
            self.viewer.update_processed(self.image)
        self.update_detector()

    def load_mask(self):
        filename, basename, mask = load_image(self)
        if filename is None:
            return
        if self.image.shape[:-1] != mask.shape[:-1]:
            QMessageBox.critical(
                self, self.tr('Error'),
                self.tr('Both image and mask must have the same size!'))
            return
        _, self.mask = cv.threshold(cv.cvtColor(mask, cv.COLOR_BGR2GRAY), 0, 1,
                                    cv.THRESH_BINARY)
        self.onoff_button.setEnabled(True)
        self.onoff_button.setChecked(True)
        self.mask_button.setText('"{}"'.format(splitext(basename)[0]))
        self.mask_button.setToolTip(self.tr('Current detection mask image'))

    def update_detector(self):
        self.total = self.kpts = self.desc = self.matches = self.clusters = None
        self.status_label.setText('')
        self.process_button.setEnabled(True)

    def update_matching(self):
        self.matches = self.clusters = None
        self.process_button.setEnabled(True)

    def update_cluster(self):
        self.clusters = None
        self.process_button.setEnabled(True)

    def cancel(self):
        self.canceled = True
        self.status_label.setText(self.tr('Processing interrupted!'))
        modify_font(self.status_label, bold=False, italic=False)

    def process(self):
        start = time()
        self.canceled = False
        self.status_label.setText(self.tr('Processing, please wait...'))
        algorithm = self.detector_combo.currentIndex()
        response = 100 - self.response_spin.value()
        matching = self.matching_spin.value() / 100 * 255
        distance = self.distance_spin.value() / 100
        cluster = self.cluster_spin.value()
        modify_font(self.status_label, bold=False, italic=True)
        QCoreApplication.processEvents()

        if self.kpts is None:
            if algorithm == 0:
                detector = cv.BRISK_create()
            elif algorithm == 1:
                detector = cv.ORB_create()
            elif algorithm == 2:
                detector = cv.AKAZE_create()
            else:
                return
            mask = self.mask if self.onoff_button.isChecked() else None
            self.kpts, self.desc = detector.detectAndCompute(self.gray, mask)
            self.total = len(self.kpts)
            responses = np.array([k.response for k in self.kpts])
            strongest = (cv.normalize(responses, None, 0, 100, cv.NORM_MINMAX)
                         >= response).flatten()
            self.kpts = list(compress(self.kpts, strongest))
            if len(self.kpts) > 30000:
                QMessageBox.warning(
                    self, self.tr('Warning'),
                    self.
                    tr('Too many keypoints found ({}), please reduce response value'
                       .format(self.total)))
                self.kpts = self.desc = None
                self.total = 0
                self.status_label.setText('')
                return
            self.desc = self.desc[strongest]

        if self.matches is None:
            matcher = cv.BFMatcher_create(cv.NORM_HAMMING, True)
            self.matches = matcher.radiusMatch(self.desc, self.desc, matching)
            if self.matches is None:
                self.status_label.setText(
                    self.tr('No keypoint match found with current settings'))
                modify_font(self.status_label, italic=False, bold=True)
                return
            self.matches = [
                item for sublist in self.matches for item in sublist
            ]
            self.matches = [
                m for m in self.matches if m.queryIdx != m.trainIdx
            ]

        if not self.matches:
            self.clusters = []
        elif self.clusters is None:
            self.clusters = []
            min_dist = distance * np.min(self.gray.shape) / 2
            kpts_a = np.array([p.pt for p in self.kpts])
            ds = np.linalg.norm([
                kpts_a[m.queryIdx] - kpts_a[m.trainIdx] for m in self.matches
            ],
                                axis=1)
            self.matches = [
                m for i, m in enumerate(self.matches) if ds[i] > min_dist
            ]

            total = len(self.matches)
            progress = QProgressDialog(self.tr('Clustering matches...'),
                                       self.tr('Cancel'), 0, total, self)
            progress.canceled.connect(self.cancel)
            progress.setWindowModality(Qt.WindowModal)
            for i in range(total):
                match0 = self.matches[i]
                d0 = ds[i]
                query0 = match0.queryIdx
                train0 = match0.trainIdx
                group = [match0]

                for j in range(i + 1, total):
                    match1 = self.matches[j]
                    query1 = match1.queryIdx
                    train1 = match1.trainIdx
                    if query1 == train0 and train1 == query0:
                        continue
                    d1 = ds[j]
                    if np.abs(d0 - d1) > min_dist:
                        continue

                    a0 = np.array(self.kpts[query0].pt)
                    b0 = np.array(self.kpts[train0].pt)
                    a1 = np.array(self.kpts[query1].pt)
                    b1 = np.array(self.kpts[train1].pt)

                    aa = np.linalg.norm(a0 - a1)
                    bb = np.linalg.norm(b0 - b1)
                    ab = np.linalg.norm(a0 - b1)
                    ba = np.linalg.norm(b0 - a1)

                    if not (0 < aa < min_dist and 0 < bb < min_dist
                            or 0 < ab < min_dist and 0 < ba < min_dist):
                        continue
                    for g in group:
                        if g.queryIdx == train1 and g.trainIdx == query1:
                            break
                    else:
                        group.append(match1)

                if len(group) >= cluster:
                    self.clusters.append(group)
                progress.setValue(i)
                if self.canceled:
                    self.update_detector()
                    return
            progress.close()

        output = np.copy(self.image)
        hsv = np.zeros((1, 1, 3))
        nolines = self.nolines_check.isChecked()
        show_kpts = self.kpts_check.isChecked()

        if show_kpts:
            for kpt in self.kpts:
                cv.circle(output, (int(kpt.pt[0]), int(kpt.pt[1])), 2,
                          (250, 227, 72))

        angles = []
        for c in self.clusters:
            for m in c:
                ka = self.kpts[m.queryIdx]
                pa = tuple(map(int, ka.pt))
                sa = int(np.round(ka.size))
                kb = self.kpts[m.trainIdx]
                pb = tuple(map(int, kb.pt))
                sb = int(np.round(kb.size))
                angle = np.arctan2(pb[1] - pa[1], pb[0] - pa[0])
                if angle < 0:
                    angle += np.pi
                angles.append(angle)
                hsv[0, 0, 0] = angle / np.pi * 180
                hsv[0, 0, 1] = 255
                hsv[0, 0, 2] = m.distance / matching * 255
                rgb = cv.cvtColor(hsv.astype(np.uint8), cv.COLOR_HSV2BGR)
                rgb = tuple([int(x) for x in rgb[0, 0]])
                cv.circle(output, pa, sa, rgb, 1, cv.LINE_AA)
                cv.circle(output, pb, sb, rgb, 1, cv.LINE_AA)
                if not nolines:
                    cv.line(output, pa, pb, rgb, 1, cv.LINE_AA)

        regions = 0
        if angles:
            angles = np.reshape(np.array(angles, dtype=np.float32),
                                (len(angles), 1))
            if np.std(angles) < 0.1:
                regions = 1
            else:
                criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER,
                            10, 1.0)
                attempts = 10
                flags = cv.KMEANS_PP_CENTERS
                compact = [
                    cv.kmeans(angles, k, None, criteria, attempts, flags)[0]
                    for k in range(1, 11)
                ]
                compact = cv.normalize(np.array(compact), None, 0, 1,
                                       cv.NORM_MINMAX)
                regions = np.argmax(compact < 0.005) + 1
        self.viewer.update_processed(output)
        self.process_button.setEnabled(False)
        modify_font(self.status_label, italic=False, bold=True)
        self.status_label.setText(
            self.
            tr('Keypoints: {} --> Filtered: {} --> Matches: {} --> Clusters: {} --> Regions: {}'
               .format(self.total, len(self.kpts), len(self.matches),
                       len(self.clusters), regions)))
        self.info_message.emit(
            self.tr('Copy-Move Forgery = {}'.format(elapsed_time(start))))
Example #28
0
class DebugControlsWidget(QToolBar):
    def __init__(self, parent, name, data, debug_state):
        if not type(data) == binaryninja.binaryview.BinaryView:
            raise Exception('expected widget data to be a BinaryView')

        self.bv = data
        self.debug_state = debug_state

        QToolBar.__init__(self, parent)

        # TODO: Is there a cleaner way to do this?
        self.setStyleSheet("""
		QToolButton{padding: 4px 14px 4px 14px; font-size: 14pt;}
		QToolButton:disabled{color: palette(alternate-base)}
		""")

        self.actionRun = QAction("Run", self)
        self.actionRun.triggered.connect(lambda: self.perform_run())
        self.actionRestart = QAction("Restart", self)
        self.actionRestart.triggered.connect(lambda: self.perform_restart())
        self.actionQuit = QAction("Quit", self)
        self.actionQuit.triggered.connect(lambda: self.perform_quit())
        self.actionAttach = QAction("Attach", self)
        self.actionAttach.triggered.connect(lambda: self.perform_attach())
        self.actionDetach = QAction("Detach", self)
        self.actionDetach.triggered.connect(lambda: self.perform_detach())
        self.actionSettings = QAction("Settings...", self)
        self.actionSettings.triggered.connect(lambda: self.perform_settings())
        self.actionPause = QAction("Pause", self)
        self.actionPause.triggered.connect(lambda: self.perform_pause())
        self.actionResume = QAction("Resume", self)
        self.actionResume.triggered.connect(lambda: self.perform_resume())
        self.actionStepIntoAsm = QAction("Step Into (Assembly)", self)
        self.actionStepIntoAsm.triggered.connect(
            lambda: self.perform_step_into_asm())
        self.actionStepIntoIL = QAction("Step Into", self)
        self.actionStepIntoIL.triggered.connect(
            lambda: self.perform_step_into_il())
        self.actionStepOverAsm = QAction("Step Over (Assembly)", self)
        self.actionStepOverAsm.triggered.connect(
            lambda: self.perform_step_over_asm())
        self.actionStepOverIL = QAction("Step Over", self)
        self.actionStepOverIL.triggered.connect(
            lambda: self.perform_step_over_il())
        self.actionStepReturn = QAction("Step Return", self)
        self.actionStepReturn.triggered.connect(
            lambda: self.perform_step_return())

        # session control menu
        self.controlMenu = QMenu("Process Control", self)
        self.controlMenu.addAction(self.actionRun)
        self.controlMenu.addAction(self.actionRestart)
        self.controlMenu.addAction(self.actionQuit)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionAttach)
        self.controlMenu.addAction(self.actionDetach)
        self.controlMenu.addSeparator()
        self.controlMenu.addAction(self.actionSettings)

        self.stepIntoMenu = QMenu("Step Into", self)
        self.stepIntoMenu.addAction(self.actionStepIntoIL)
        self.stepIntoMenu.addAction(self.actionStepIntoAsm)

        self.stepOverMenu = QMenu("Step Over", self)
        self.stepOverMenu.addAction(self.actionStepOverIL)
        self.stepOverMenu.addAction(self.actionStepOverAsm)

        self.btnControl = QToolButton(self)
        self.btnControl.setMenu(self.controlMenu)
        self.btnControl.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnControl.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnControl.setDefaultAction(self.actionRun)
        self.addWidget(self.btnControl)

        # execution control buttons
        self.addAction(self.actionPause)
        self.addAction(self.actionResume)

        self.btnStepInto = QToolButton(self)
        self.btnStepInto.setMenu(self.stepIntoMenu)
        self.btnStepInto.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepInto.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepInto.setDefaultAction(self.actionStepIntoIL)
        self.addWidget(self.btnStepInto)

        self.btnStepOver = QToolButton(self)
        self.btnStepOver.setMenu(self.stepOverMenu)
        self.btnStepOver.setPopupMode(QToolButton.MenuButtonPopup)
        self.btnStepOver.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.btnStepOver.setDefaultAction(self.actionStepOverIL)
        self.addWidget(self.btnStepOver)

        # TODO: Step until returning from current function
        self.addAction(self.actionStepReturn)

        self.threadMenu = QMenu("Threads", self)

        self.btnThreads = QToolButton(self)
        self.btnThreads.setMenu(self.threadMenu)
        self.btnThreads.setPopupMode(QToolButton.InstantPopup)
        self.btnThreads.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.addWidget(self.btnThreads)

        self.set_thread_list([])

        self.editStatus = QLineEdit('INACTIVE', self)
        self.editStatus.setReadOnly(True)
        self.editStatus.setAlignment(QtCore.Qt.AlignCenter)
        self.addWidget(self.editStatus)

        # disable buttons
        self.set_actions_enabled(Run=self.can_exec(),
                                 Restart=False,
                                 Quit=False,
                                 Attach=self.can_connect(),
                                 Detach=False,
                                 Pause=False,
                                 Resume=False,
                                 StepInto=False,
                                 StepOver=False,
                                 StepReturn=False)
        self.set_resume_pause_action("Pause")
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")

    def __del__(self):
        # TODO: Move this elsewhere
        # This widget is tasked with cleaning up the state after the view is closed
        # binjaplug.delete_state(self.bv)
        pass

    def can_exec(self):
        return DebugAdapter.ADAPTER_TYPE.use_exec(
            self.debug_state.adapter_type)

    def can_connect(self):
        return DebugAdapter.ADAPTER_TYPE.use_connect(
            self.debug_state.adapter_type)

    def perform_run(self):
        def perform_run_thread():
            try:
                self.debug_state.run()
                execute_on_main_thread_and_wait(perform_run_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_run_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(
                    lambda: perform_run_error('ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_run_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_run_error(e):
            self.state_error(e)

        self.state_starting('STARTING')
        threading.Thread(target=perform_run_thread).start()

    def perform_restart(self):
        def perform_restart_thread():
            try:
                self.debug_state.restart()
                execute_on_main_thread_and_wait(perform_restart_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_restart_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(lambda: perform_restart_error(
                    'ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_restart_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_restart_error(e):
            self.state_error(e)

        self.state_starting('RESTARTING')
        threading.Thread(target=perform_restart_thread).start()

    def perform_quit(self):
        self.debug_state.quit()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_attach(self):
        def perform_attach_thread():
            try:
                self.debug_state.attach()
                execute_on_main_thread_and_wait(perform_attach_after)
            except ConnectionRefusedError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except TimeoutError:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: Connection Refused'))
            except Exception as e:
                execute_on_main_thread_and_wait(
                    lambda: perform_attach_error('ERROR: ' + ' '.join(e.args)))
                traceback.print_exc(file=sys.stderr)

        def perform_attach_after():
            self.state_stopped()
            self.debug_state.ui.on_step()

        def perform_attach_error(e):
            self.state_error(e)

        self.state_starting('ATTACHING')
        threading.Thread(target=perform_attach_thread).start()

    def perform_detach(self):
        self.debug_state.detach()
        self.state_inactive()
        self.debug_state.ui.on_step()

    def perform_settings(self):
        def settings_finished():
            if self.debug_state.running:
                self.state_running()
            elif self.debug_state.connected:
                local_rip = self.debug_state.local_ip
                if self.debug_state.bv.read(local_rip, 1) and len(
                        self.debug_state.bv.get_functions_containing(
                            local_rip)) > 0:
                    self.state_stopped()
                else:
                    self.state_stopped_extern()
            else:
                self.state_inactive()

        dialog = AdapterSettingsDialog.AdapterSettingsDialog(self, self.bv)
        dialog.show()
        dialog.finished.connect(settings_finished)

    def perform_pause(self):
        self.debug_state.pause()
        # Don't update state here-- one of the other buttons is running in a thread and updating for us

    def perform_resume(self):
        def perform_resume_thread():
            (reason, data) = self.debug_state.go()
            execute_on_main_thread_and_wait(
                lambda: perform_resume_after(reason, data))

        def perform_resume_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_running()
        threading.Thread(target=perform_resume_thread).start()

    def perform_step_into_asm(self):
        def perform_step_into_asm_thread():
            (reason, data) = self.debug_state.step_into()
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_asm_after(reason, data))

        def perform_step_into_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_asm_thread).start()

    def perform_step_into_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_into_il_thread():
            (reason, data) = self.debug_state.step_into(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_into_il_after(reason, data))

        def perform_step_into_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_into_il_thread).start()

    def perform_step_over_asm(self):
        def perform_step_over_asm_thread():
            (reason, data) = self.debug_state.step_over()
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_asm_after(reason, data))

        def perform_step_over_asm_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_asm_thread).start()

    def perform_step_over_il(self):

        disasm = self.debug_state.ui.debug_view.binary_editor.getDisassembly()
        graph_type = disasm.getGraphType()

        def perform_step_over_il_thread():
            (reason, data) = self.debug_state.step_over(graph_type)
            execute_on_main_thread_and_wait(
                lambda: perform_step_over_il_after(reason, data))

        def perform_step_over_il_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_over_il_thread).start()

    def perform_step_return(self):
        def perform_step_return_thread():
            (reason, data) = self.debug_state.step_return()
            execute_on_main_thread_and_wait(
                lambda: perform_step_return_after(reason, data))

        def perform_step_return_after(reason, data):
            self.handle_stop_return(reason, data)
            self.debug_state.ui.on_step()

        self.state_busy("STEPPING")
        threading.Thread(target=perform_step_return_thread).start()

    def set_actions_enabled(self, **kwargs):
        def enable_step_into(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)

        def enable_step_over(e):
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)

        def enable_starting(e):
            self.actionRun.setEnabled(e and self.can_exec())
            self.actionAttach.setEnabled(e and self.can_connect())

        def enable_stopping(e):
            self.actionRestart.setEnabled(e)
            self.actionQuit.setEnabled(e)
            self.actionDetach.setEnabled(e)

        def enable_stepping(e):
            self.actionStepIntoAsm.setEnabled(e)
            self.actionStepIntoIL.setEnabled(e)
            self.actionStepOverAsm.setEnabled(e)
            self.actionStepOverIL.setEnabled(e)
            self.actionStepReturn.setEnabled(e)

        actions = {
            "Run": lambda e: self.actionRun.setEnabled(e),
            "Restart": lambda e: self.actionRestart.setEnabled(e),
            "Quit": lambda e: self.actionQuit.setEnabled(e),
            "Attach": lambda e: self.actionAttach.setEnabled(e),
            "Detach": lambda e: self.actionDetach.setEnabled(e),
            "Pause": lambda e: self.actionPause.setEnabled(e),
            "Resume": lambda e: self.actionResume.setEnabled(e),
            "StepInto": enable_step_into,
            "StepOver": enable_step_over,
            "StepReturn": lambda e: self.actionStepReturn.setEnabled(e),
            "Threads": lambda e: self.btnThreads.setEnabled(e),
            "Starting": enable_starting,
            "Stopping": enable_stopping,
            "Stepping": enable_stepping,
        }
        for (action, enabled) in kwargs.items():
            actions[action](enabled)

    def set_default_process_action(self, action):
        actions = {
            "Run": self.actionRun,
            "Restart": self.actionRestart,
            "Quit": self.actionQuit,
            "Attach": self.actionAttach,
            "Detach": self.actionDetach,
        }
        self.btnControl.setDefaultAction(actions[action])

    def set_resume_pause_action(self, action):
        self.actionResume.setVisible(action == "Resume")
        self.actionPause.setVisible(action == "Pause")

    def set_thread_list(self, threads):
        def select_thread_fn(tid):
            def select_thread(tid):
                stateObj = binjaplug.get_state(self.bv)
                if stateObj.state == 'STOPPED':
                    adapter = stateObj.adapter
                    adapter.thread_select(tid)
                    self.debug_state.ui.context_display()
                else:
                    print('cannot set thread in state %s' % stateObj.state)

            return lambda: select_thread(tid)

        self.threadMenu.clear()
        if len(threads) > 0:
            for thread in threads:
                item_name = "Thread {} at {}".format(thread['tid'],
                                                     hex(thread['ip']))
                action = self.threadMenu.addAction(
                    item_name, select_thread_fn(thread['tid']))
                if thread['selected']:
                    self.btnThreads.setDefaultAction(action)
        else:
            defaultThreadAction = self.threadMenu.addAction("Thread List")
            defaultThreadAction.setEnabled(False)
            self.btnThreads.setDefaultAction(defaultThreadAction)

    def state_starting(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=False,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_inactive(self, msg=None):
        self.editStatus.setText(msg or 'INACTIVE')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Stepping=False,
                                 Pause=False,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Pause")

    def state_stopped(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_stopped_extern(self, msg=None):
        self.editStatus.setText(msg or 'STOPPED')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=True,
                                 StepReturn=False,
                                 Pause=True,
                                 Resume=True,
                                 Threads=True)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Resume")

    def state_running(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_busy(self, msg=None):
        self.editStatus.setText(msg or 'RUNNING')
        self.set_actions_enabled(Starting=False,
                                 Stopping=True,
                                 Stepping=False,
                                 Pause=True,
                                 Resume=False,
                                 Threads=False)
        self.set_default_process_action("Quit")
        self.set_resume_pause_action("Pause")

    def state_error(self, msg=None):
        self.editStatus.setText(msg or 'ERROR')
        self.set_actions_enabled(Starting=True,
                                 Stopping=False,
                                 Pause=False,
                                 Resume=False,
                                 Stepping=False,
                                 Threads=False)
        self.set_default_process_action(
            "Attach" if self.can_connect() else "Run")
        self.set_thread_list([])
        self.set_resume_pause_action("Resume")

    def handle_stop_return(self, reason, data):
        if reason == DebugAdapter.STOP_REASON.STDOUT_MESSAGE:
            self.state_stopped('stdout: ' + data)
        elif reason == DebugAdapter.STOP_REASON.PROCESS_EXITED:
            self.debug_state.quit()
            self.state_inactive('process exited, return code=%d' % data)
        elif reason == DebugAdapter.STOP_REASON.BACKEND_DISCONNECTED:
            self.debug_state.quit()
            self.state_inactive('backend disconnected (process exited?)')
Example #29
0
    def __init__(
        self,
        document: Optional[vp.Document] = None,
        view_mode: ViewMode = ViewMode.PREVIEW,
        show_pen_up: bool = False,
        show_points: bool = False,
        parent=None,
    ):
        super().__init__(parent)

        self._settings = QSettings()
        self._settings.beginGroup("viewer")

        self.setWindowTitle("vpype viewer")
        self.setStyleSheet("""
        QToolButton:pressed {
            background-color: rgba(0, 0, 0, 0.2);
        }
        """)

        self._viewer_widget = QtViewerWidget(parent=self)

        # setup toolbar
        self._toolbar = QToolBar()
        self._icon_size = QSize(32, 32)
        self._toolbar.setIconSize(self._icon_size)

        view_mode_grp = QActionGroup(self._toolbar)
        if _DEBUG_ENABLED:
            act = view_mode_grp.addAction("None")
            act.setCheckable(True)
            act.setChecked(view_mode == ViewMode.NONE)
            act.triggered.connect(
                functools.partial(self.set_view_mode, ViewMode.NONE))
        act = view_mode_grp.addAction("Outline Mode")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.OUTLINE)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.OUTLINE))
        act = view_mode_grp.addAction("Outline Mode (Colorful)")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.OUTLINE_COLORFUL)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.OUTLINE_COLORFUL))
        act = view_mode_grp.addAction("Preview Mode")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.PREVIEW)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.PREVIEW))
        self.set_view_mode(view_mode)

        # VIEW MODE
        # view modes
        view_mode_btn = QToolButton()
        view_mode_menu = QMenu(view_mode_btn)
        act = view_mode_menu.addAction("View Mode:")
        act.setEnabled(False)
        view_mode_menu.addActions(view_mode_grp.actions())
        view_mode_menu.addSeparator()
        # show pen up
        act = view_mode_menu.addAction("Show Pen-Up Trajectories")
        act.setCheckable(True)
        act.setChecked(show_pen_up)
        act.toggled.connect(self.set_show_pen_up)
        self._viewer_widget.engine.show_pen_up = show_pen_up
        # show points
        act = view_mode_menu.addAction("Show Points")
        act.setCheckable(True)
        act.setChecked(show_points)
        act.toggled.connect(self.set_show_points)
        self._viewer_widget.engine.show_points = show_points
        # preview mode options
        view_mode_menu.addSeparator()
        act = view_mode_menu.addAction("Preview Mode Options:")
        act.setEnabled(False)
        # pen width
        pen_width_menu = view_mode_menu.addMenu("Pen Width")
        act_grp = PenWidthActionGroup(0.3, parent=pen_width_menu)
        act_grp.triggered.connect(self.set_pen_width_mm)
        pen_width_menu.addActions(act_grp.actions())
        self.set_pen_width_mm(0.3)
        # pen opacity
        pen_opacity_menu = view_mode_menu.addMenu("Pen Opacity")
        act_grp = PenOpacityActionGroup(0.8, parent=pen_opacity_menu)
        act_grp.triggered.connect(self.set_pen_opacity)
        pen_opacity_menu.addActions(act_grp.actions())
        self.set_pen_opacity(0.8)
        # debug view
        if _DEBUG_ENABLED:
            act = view_mode_menu.addAction("Debug View")
            act.setCheckable(True)
            act.toggled.connect(self.set_debug)
        # rulers
        view_mode_menu.addSeparator()
        act = view_mode_menu.addAction("Show Rulers")
        act.setCheckable(True)
        val = bool(self._settings.value("show_rulers", True))
        act.setChecked(val)
        act.toggled.connect(self.set_show_rulers)
        self._viewer_widget.engine.show_rulers = val
        # units
        units_menu = view_mode_menu.addMenu("Units")
        unit_action_grp = QActionGroup(units_menu)
        unit_type = UnitType(self._settings.value("unit_type",
                                                  UnitType.METRIC))
        act = unit_action_grp.addAction("Metric")
        act.setCheckable(True)
        act.setChecked(unit_type == UnitType.METRIC)
        act.setData(UnitType.METRIC)
        act = unit_action_grp.addAction("Imperial")
        act.setCheckable(True)
        act.setChecked(unit_type == UnitType.IMPERIAL)
        act.setData(UnitType.IMPERIAL)
        act = unit_action_grp.addAction("Pixel")
        act.setCheckable(True)
        act.setChecked(unit_type == UnitType.PIXELS)
        act.setData(UnitType.PIXELS)
        unit_action_grp.triggered.connect(self.set_unit_type)
        units_menu.addActions(unit_action_grp.actions())
        self._viewer_widget.engine.unit_type = unit_type

        view_mode_btn.setMenu(view_mode_menu)
        view_mode_btn.setIcon(load_icon("eye-outline.svg"))
        view_mode_btn.setText("View")
        view_mode_btn.setPopupMode(QToolButton.InstantPopup)
        view_mode_btn.setStyleSheet(
            "QToolButton::menu-indicator { image: none; }")
        self._toolbar.addWidget(view_mode_btn)

        # LAYER VISIBILITY
        self._layer_visibility_btn = QToolButton()
        self._layer_visibility_btn.setIcon(
            load_icon("layers-triple-outline.svg"))
        self._layer_visibility_btn.setText("Layer")
        self._layer_visibility_btn.setMenu(QMenu(self._layer_visibility_btn))
        self._layer_visibility_btn.setPopupMode(QToolButton.InstantPopup)
        self._layer_visibility_btn.setStyleSheet(
            "QToolButton::menu-indicator { image: none; }")
        self._toolbar.addWidget(self._layer_visibility_btn)

        # FIT TO PAGE
        fit_act = self._toolbar.addAction(load_icon("fit-to-page-outline.svg"),
                                          "Fit")
        fit_act.triggered.connect(self._viewer_widget.engine.fit_to_viewport)

        # RULER
        # TODO: not implemented yet
        # self._toolbar.addAction(load_icon("ruler-square.svg"), "Units")

        # MOUSE COORDINATES>
        self._mouse_coord_lbl = QLabel("")
        self._mouse_coord_lbl.setMargin(6)
        self._mouse_coord_lbl.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        self._mouse_coord_lbl.setSizePolicy(QSizePolicy.Expanding,
                                            QSizePolicy.Minimum)
        self._toolbar.addWidget(self._mouse_coord_lbl)
        # noinspection PyUnresolvedReferences
        self._viewer_widget.mouse_coords.connect(
            self.set_mouse_coords)  # type: ignore

        # setup horizontal layout for optional side widgets
        self._hlayout = QHBoxLayout()
        self._hlayout.setSpacing(0)
        self._hlayout.setMargin(0)
        self._hlayout.addWidget(self._viewer_widget)
        widget = QWidget()
        widget.setLayout(self._hlayout)

        # setup global vertical layout
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setMargin(0)
        layout.addWidget(self._toolbar)
        layout.addWidget(widget)
        self.setLayout(layout)

        if document is not None:
            self.set_document(document)
Example #30
0
    def __init__(self):
        super(SideBar, self).__init__()
        # read StyleSheet
        with open('QSS/sideBar.qss', 'r') as f:
            self.setStyleSheet(f.read())
            f.close()
        # Vertical Layout
        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)

        # Icons
        camera_icon = ico.Icon('font-awesome:solid:camera-retro',
                               QColor('#E91E63'))
        database_icon = ico.Icon('font-awesome:solid:database',
                                 QColor('#03A9F4'))
        about_icon = ico.Icon('font-awesome:solid:exclamation-triangle',
                              QColor('#FDD835'))

        # logo image
        logo = QLabel()
        logo.setFixedSize(110, 110)
        logo_img = QPixmap('resources/qt.png')
        logo.setPixmap(logo_img)
        logo.setContentsMargins(20, 20, 20, 20)

        # bottom label like spacer
        bottom_label = QLabel()

        # Create Buttons
        btn_width = 110
        btn_height = 40

        self.Camera = QToolButton()
        self.Camera.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.Camera.setObjectName('Camera_btn')
        self.Camera.setText(' Camera')
        self.Camera.setFixedSize(btn_width, btn_height)
        self.Camera.setIcon(camera_icon)
        self.Camera.setIconSize(QSize(20, 20))

        self.DataBase = QToolButton()
        self.DataBase.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.DataBase.setObjectName('customers_btn')
        self.DataBase.setText(' DataBase')
        self.DataBase.setFixedSize(btn_width, btn_height)
        self.DataBase.setIcon(database_icon)
        self.DataBase.setIconSize(QSize(20, 20))

        self.About = QToolButton()
        self.About.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self.About.setObjectName('About_btn')
        self.About.setText(' About')
        self.About.setFixedSize(btn_width, btn_height)
        self.About.setIcon(about_icon)
        self.About.setIconSize(QSize(20, 20))

        # add widget
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.addWidget(logo)
        self.verticalLayout.addWidget(self.Camera)
        self.verticalLayout.addWidget(self.DataBase)
        self.verticalLayout.addWidget(self.About)
        self.verticalLayout.addWidget(bottom_label)
        self.setLayout(self.verticalLayout)
Example #31
0
    def _create_ammo_pickup_boxes(self, size_policy, item_database: ItemDatabase):
        """
        Creates the GroupBox with SpinBoxes for selecting the pickup count of all the ammo
        :param item_database:
        :return:
        """

        self._ammo_maximum_spinboxes = collections.defaultdict(list)
        self._ammo_pickup_widgets = {}

        resource_database = default_prime2_resource_database()

        for ammo in item_database.ammo.values():
            title_layout = QHBoxLayout()
            title_layout.setObjectName(f"{ammo.name} Title Horizontal Layout")

            expand_ammo_button = QToolButton(self.ammo_box)
            expand_ammo_button.setGeometry(QRect(20, 30, 24, 21))
            expand_ammo_button.setText("+")
            title_layout.addWidget(expand_ammo_button)

            category_label = QLabel(self.ammo_box)
            category_label.setSizePolicy(size_policy)
            category_label.setText(ammo.name + "s")
            title_layout.addWidget(category_label)

            pickup_box = QGroupBox(self.ammo_box)
            pickup_box.setSizePolicy(size_policy)
            layout = QGridLayout(pickup_box)
            layout.setObjectName(f"{ammo.name} Box Layout")
            current_row = 0

            for ammo_item in ammo.items:
                item = resource_database.get_by_type_and_index(ResourceType.ITEM, ammo_item)

                target_count_label = QLabel(pickup_box)
                target_count_label.setText(f"{item.long_name} Target" if len(ammo.items) > 1 else "Target count")

                maximum_spinbox = QSpinBox(pickup_box)
                maximum_spinbox.setMaximum(ammo.maximum)
                maximum_spinbox.valueChanged.connect(partial(self._on_update_ammo_maximum_spinbox, ammo_item))
                self._ammo_maximum_spinboxes[ammo_item].append(maximum_spinbox)

                layout.addWidget(target_count_label, current_row, 0)
                layout.addWidget(maximum_spinbox, current_row, 1)
                current_row += 1

            count_label = QLabel(pickup_box)
            count_label.setText("Pickup Count")
            count_label.setToolTip("How many instances of this expansion should be placed.")

            pickup_spinbox = QSpinBox(pickup_box)
            pickup_spinbox.setMaximum(AmmoState.maximum_pickup_count())
            pickup_spinbox.valueChanged.connect(partial(self._on_update_ammo_pickup_spinbox, ammo))

            layout.addWidget(count_label, current_row, 0)
            layout.addWidget(pickup_spinbox, current_row, 1)
            current_row += 1

            if ammo.temporaries:
                require_major_item_check = QCheckBox(pickup_box)
                require_major_item_check.setText("Requires the major item to work?")
                require_major_item_check.stateChanged.connect(partial(self._on_update_ammo_require_major_item, ammo))
                layout.addWidget(require_major_item_check, current_row, 0, 1, 2)
                current_row += 1
            else:
                require_major_item_check = None

            expected_count = QLabel(pickup_box)
            expected_count.setWordWrap(True)
            expected_count.setText(_EXPECTED_COUNT_TEXT_TEMPLATE)
            expected_count.setToolTip("Some expansions may provide 1 extra, even with no variance, if the total count "
                                      "is not divisible by the pickup count.")
            layout.addWidget(expected_count, current_row, 0, 1, 2)
            current_row += 1

            self._ammo_pickup_widgets[ammo] = (pickup_spinbox, expected_count, expand_ammo_button,
                                               category_label, pickup_box, require_major_item_check)

            expand_ammo_button.clicked.connect(partial(_toggle_box_visibility, expand_ammo_button, pickup_box))
            pickup_box.setVisible(False)

            self.ammo_layout.addLayout(title_layout)
            self.ammo_layout.addWidget(pickup_box)
Example #32
0
class GeneratedCurvePage(DetailsPageBase):
    def __init__(self, parent=None):
        super().__init__(parent, 'synthetic curve')
        self.gen_curve: Optional[WdGeneratedValues] = None
        self.segments_edit_semaphore = Lock()

        layout = QVBoxLayout()
        group_segments = QGroupBox('Segments')
        layout_segments = QVBoxLayout()
        layout_segments_hdr = QHBoxLayout()
        layout_segments_hdr_labels = QVBoxLayout()
        layout_segments_hdr_labels.addWidget(
            QLabel('Segments are calculated in parallel'))
        layout_segments_hdr_labels.addWidget(
            QLabel('and may have different density (PHIN)'))
        layout_segments_hdr.addLayout(layout_segments_hdr_labels)
        layout_segments_hdr.addStretch()
        self.add_segment_button = QToolButton()
        self.add_segment_button.setText('+')
        self.del_segment_button = QToolButton()
        self.del_segment_button.setText('-')
        layout_segments_hdr.addWidget(self.add_segment_button)
        layout_segments_hdr.addWidget(self.del_segment_button)
        layout_segments.addLayout(layout_segments_hdr)
        self.table_segments = QTableWidget(1, 3)
        self.table_segments.setHorizontalHeaderLabels(
            ['phase from', 'phase to', 'PHIN phase incr'])
        layout_segments.addWidget(self.table_segments)
        group_segments.setLayout(layout_segments)
        layout.addWidget(group_segments)
        self.setLayout(layout)

        self.table_segments.itemActivated.connect(self._on_segment_activated)
        self.table_segments.itemChanged.connect(self._on_segment_item_changed)
        self.add_segment_button.clicked.connect(self._on_segment_add_clicked)
        self.del_segment_button.clicked.connect(self._on_segment_del_clicked)

    def is_active_for_item(
            self, item: Optional[Container]) -> (bool, Optional[Container]):
        """Returns pair: should_be_active and item (may be e.g. parent of provided item)"""
        try:
            if isinstance(item, CurveGeneratedContainer):
                return True, item
            elif isinstance(item.parent(), CurveGeneratedContainer):
                return True, item.parent()
            else:
                return False, None
        except AttributeError:
            return False, None

    @Slot(QTableWidgetItem)
    def _on_segment_activated(self, item: QTableWidgetItem):
        self.del_segment_button.setEnabled(self.table_segments.rowCount() > 1)
        self.add_segment_button.setEnabled(
            self.table_segments.rowCount() <= 20)

    @Slot(QTableWidgetItem)
    def _on_segment_item_changed(self, item: QTableWidgetItem):
        """when user enters new value to segments table"""

        if self.segments_edit_semaphore.locked(
        ):  # item is being updated from code rather than by user
            return

        val = float(item.text())
        logger().info(f'Segment value changed: {val}')
        if item.column() == 0:
            self.gen_curve.segment_set_range(segment=item.row(),
                                             from_value=val)
        elif item.column() == 1:
            self.gen_curve.segment_set_range(segment=item.row(), to_value=val)
        elif item.column() == 2:
            self.gen_curve.segment_update_data(segment=item.row(),
                                               data={'PHIN': val})
        self.populate_segments()

    @Slot(bool)
    def _on_segment_add_clicked(self, selected: bool):
        """on delete segment button"""
        row = self.table_segments.currentRow()
        if row is None:
            return
        self.gen_curve.segment_split(row)
        self.populate_segments()

    @Slot(bool)
    def _on_segment_del_clicked(self, selected: bool):
        """on add new segment button"""
        row = self.table_segments.currentRow()
        if self.table_segments.rowCount() < 2 or row is None:
            return
        self.gen_curve.segment_delete(row)
        self.populate_segments()

    def item_changed(self, previous_item: Container):
        """when curved item for which details are displayed hve been changed"""
        super().item_changed(previous_item)
        if self.enabled:
            try:
                if isinstance(self.item.content, WdGeneratedValues):
                    self.gen_curve = self.item.content
                else:
                    self.gen_curve = None
            except AttributeError:
                self.gen_curve = None
            self.populate_segments()
        # try:
        #     df = self.item.get_df()
        # except AttributeError:
        #     df = None
        # self.pandas.setDataFrame(df)

    def populate_segments(self):
        if self.gen_curve is None:
            self.table_segments.setRowCount(0)
            return
        self.table_segments.setRowCount(self.gen_curve.segments_count())
        for n in range(self.gen_curve.segments_count()):
            start, stop = self.gen_curve.segment_range(n)
            phin = self.gen_curve.segment_get_data(n, 'PHIN')
            self.set_segment_text(n, 0, start)
            self.set_segment_text(n, 1, stop)
            self.set_segment_text(n, 2, phin)
        self.table_segments.resizeRowsToContents()

    def set_segment_text(self, row, col, txt):
        with self.segments_edit_semaphore:
            item = self.table_segments.item(row, col)
            if item is None:
                item = QTableWidgetItem()
                self.table_segments.setItem(row, col, item)
            item.setText(str(txt))