Example #1
0
class NameList(QDockWidget):
    def __init__(self, window):
        super(NameList, self).__init__('Current Plots')
        self.namelist_model = QStandardItemModel()
        self.namelist_view = QListView()
        self.namelist_view.setModel(self.namelist_model)
        self.setWidget(self.namelist_view)
        self.window = window
        self.plot_dict = {}

        self.namelist_view.doubleClicked.connect(self.activate_item)
        self.namelist_view.setContextMenuPolicy(QtConst.ActionsContextMenu)
        delete_action = QAction("Delete Selected", self.namelist_view)
        ###
        pause_action = QAction("Stop Script", self.namelist_view)
        delete_action.triggered.connect(self.delete_item)
        pause_action.triggered.connect(self.pause)
        self.namelist_view.addAction(delete_action)
        ###
        self.namelist_view.addAction(pause_action)

    def activate_item(self, index):
        item = self.namelist_model.itemFromIndex(index)
        plot = self.plot_dict[str(item.text())]
        if plot.closed:
            plot.closed = False
            self.window.add_plot(plot)

    def delete_item(self):
        index = self.namelist_view.currentIndex()
        item = self.namelist_model.itemFromIndex(index)
        del self[str(item.text())]

    def pause(self):
        sock = socket.socket()
        sock.connect(('localhost', 9091))
        sock.send(b'stop')
        sock.close()

    def __getitem__(self, item):
        return self.plot_dict[item]

    def __setitem__(self, name, plot):
        model = QStandardItem(name)
        model.setEditable(False)
        self.namelist_model.appendRow(model)
        self.plot_dict[name] = plot

    def __contains__(self, value):
        return value in self.plot_dict

    def __delitem__(self, name):
        self.namelist_model.removeRow(self.namelist_model.findItems(name)[0].index().row())
        self.plot_dict[name].close()
        del self.plot_dict[name]

    def keys(self):
        return list(self.plot_dict.keys());
Example #2
0
class ListViewer(QWidget):

    def __init__(self, title, content):
        QWidget.__init__(self)
        self.ui(title, content)

    def ui(self, title, content):
        self.setWindowTitle(title)
        from modules.constant import ICON_PATH
        self.setWindowIcon(QIcon(ICON_PATH[0]))
        self.resize(self.width(), self.height())
        layout = QVBoxLayout(self)
        self.list_view = QListView(self)
        self.list_view.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.list_view.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.model = QStandardItemModel()
        for item in content:
            self.model.appendRow(QStandardItem(item))
        self.list_view.setModel(self.model)
        layout.addWidget(self.list_view)
        self.show()

    def contextMenuEvent(self, event):
        menu = QMenu(self)
        copyAction = menu.addAction("Copy")
        action = menu.exec_(self.mapToGlobal(event.pos()))
        if action == copyAction:
            copiedStr = ' '.join([self.model.itemFromIndex(idx).text() for idx in self.list_view.selectedIndexes()])
            import pyperclip
            pyperclip.copy(copiedStr)
Example #3
0
class MainWin(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWin, self).__init__(parent)
        self.setupUi(self)
        self.setWindowTitle("快速BI工具")
        self.showMaximized()

        self.dataSourceModel = QStandardItemModel()
        self.dataSourceModel.appendRow(QStandardItem("生产计划表"))
        self.dataSourceModel.appendRow(QStandardItem("销售计划表"))
        self.dataSourceModel.setHorizontalHeaderLabels(['表名'])
        self.treeView_datasource.setModel(self.dataSourceModel)
        self.treeView_datasource.clicked.connect(
            self.on_treeView_datasource_clicked)

        self.dataTableModel = QStandardItemModel()

        self.tableView_datatable.setModel(self.dataTableModel)

    def on_treeView_datasource_clicked(self, modelIndex):
        '''
        选择不同的字段
        :param modelIndex:
        :return:
        '''
        current_item = self.dataSourceModel.itemFromIndex(modelIndex)
        self.dataTableModel.setHorizontalHeaderLabels(['字段1', '字段2', '字段3'])
        for i in range(100):
            self.dataTableModel.appendRow([
                QStandardItem(str(i * 10 + 1)),
                QStandardItem(str(i * 10 + 2)),
                QStandardItem(str(i * 10 + 3))
            ])
Example #4
0
class DataPanel(QSplitter):
    def __init__(self, app):
        super(DataPanel, self).__init__(app)

        self.app = app
        self.data = {}

        self.setOrientation(Qt.Horizontal)
        self.setHandleWidth(1)

        self._key_list_model = QStandardItemModel(0, 1)
        self.key_lists = DwarfListView(parent=self.app)
        self.key_lists.setHeaderHidden(True)
        self.key_lists.setModel(self._key_list_model)
        self.key_lists.doubleClicked.connect(self.list_item_double_clicked)
        self.key_lists.setContextMenuPolicy(Qt.CustomContextMenu)
        self.key_lists.customContextMenuRequested.connect(
            self._on_context_menu)
        self.addWidget(self.key_lists)

        self.editor = QPlainTextEdit()
        self.addWidget(self.editor)

        self.hex_view = HexEditor(self.app)
        self.hex_view.setVisible(False)
        self.addWidget(self.hex_view)
        #self.setStretchFactor(0, 8)
        self.setStretchFactor(1, 4)

    def clear(self):
        self._key_list_model.clear()
        self.editor.setPlainText('')
        self.hex_view.clear_panel()

    def append_data(self, data_type, key, text_data):
        if key not in self.data:
            self._key_list_model.appendRow([QStandardItem(key)])
        self.data[key] = [data_type, text_data]

    def list_item_double_clicked(self, item):
        item = self._key_list_model.itemFromIndex(item)
        if self.data[item.text()][0] == 'plain':
            self.hex_view.setVisible(False)
            self.editor.setVisible(True)
            self.editor.setPlainText(self.data[item.text()][1])
        else:
            self.editor.setVisible(False)
            self.hex_view.setVisible(True)
            self.hex_view.bytes_per_line = 16
            self.hex_view.set_data(self.data[item.text()][1])

    def _on_context_menu(self, pos):
        context_menu = QMenu(self)

        index = self.key_lists.indexAt(pos).row()
        if index != -1:
            context_menu.addAction('Clear', self.clear)
            global_pt = self.key_lists.mapToGlobal(pos)
            context_menu.exec(global_pt)
Example #5
0
    def find_in_model(self, model: QStandardItemModel, path):
        for i in range(0, model.rowCount()):
            index_in_tree = model.index(i, 0)

            if model.itemFromIndex(index_in_tree).path == path:
                return index_in_tree

            if model.itemFromIndex(index_in_tree).is_dir:
                index_in_tree = self.find_in_model_parent(model,
                                                          path,
                                                          parent=index_in_tree)

                if not index_in_tree.isValid():
                    continue

                if model.itemFromIndex(index_in_tree).path == path:
                    return index_in_tree
Example #6
0
class labelSelector(QComboBox):
    def __init__(self):
        super(labelSelector, self).__init__()
        lisOfColors = []
        lisOfColors.append(Qt.transparent)
        lisOfColors.append(QColor(91, 173, 220))
        lisOfColors.append(QColor(151, 202, 63))
        lisOfColors.append(QColor(247, 229, 61))
        lisOfColors.append(QColor(255, 170, 63))
        lisOfColors.append(QColor(177, 102, 63))
        lisOfColors.append(QColor(238, 50, 51))
        lisOfColors.append(QColor(191, 106, 209))
        lisOfColors.append(QColor(118, 119, 114))

        self.itemModel = QStandardItemModel()
        for color in lisOfColors:
            item = QStandardItem()
            item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
            item.setCheckState(Qt.Unchecked)
            item.setText(" ")
            item.setData(color, Qt.BackgroundColorRole)
            self.itemModel.appendRow(item)
        self.setModel(self.itemModel)

    def getLabels(self):
        listOfIndexes = []
        for i in range(self.itemModel.rowCount()):
            index = self.itemModel.index(i, 0)
            item = self.itemModel.itemFromIndex(index)
            if item.checkState():
                listOfIndexes.append(i)
        return listOfIndexes

    def setLabels(self, listOfIndexes):
        for i in listOfIndexes:
            index = self.itemModel.index(i, 0)
            item = self.itemModel.itemFromIndex(index)
            item.setCheckState(True)
Example #7
0
class DataPanel(QSplitter):
    def __init__(self, app):
        super(DataPanel, self).__init__(app)

        self.app = app
        self.data = {}

        self.setOrientation(Qt.Horizontal)
        self.setHandleWidth(1)

        self._key_list_model = QStandardItemModel(0, 1)
        self.key_lists = DwarfListView(parent=self.app)
        self.key_lists.setHeaderHidden(True)
        self.key_lists.setModel(self._key_list_model)
        self.key_lists.doubleClicked.connect(self.list_item_double_clicked)

        self.addWidget(self.key_lists)

        self.editor = QPlainTextEdit()
        self.addWidget(self.editor)

        self.hex_view = HexEditor(self.app)
        self.hex_view.setVisible(False)
        self.addWidget(self.hex_view)
        #self.setStretchFactor(0, 8)
        self.setStretchFactor(1, 4)

    def clear(self):
        self._key_list_model.clear()
        self.editor.setPlainText('')
        self.hex_view.clear_panel()

    def append_data(self, data_type, key, text_data):
        if key not in self.data:
            self._key_list_model.appendRow([QStandardItem(key)])
        self.data[key] = [data_type, text_data]

    def list_item_double_clicked(self, item):
        item = self._key_list_model.itemFromIndex(item)
        if self.data[item.text()][0] == 'plain':
            self.hex_view.setVisible(False)
            self.editor.setVisible(True)
            self.editor.setPlainText(self.data[item.text()][1])
        else:
            self.editor.setVisible(False)
            self.hex_view.setVisible(True)
            self.hex_view.bytes_per_line = 16
            self.hex_view.set_data(self.data[item.text()][1])
Example #8
0
class LinklistWidget(QListView):
    
    resultSelected = pyqtSignal(str)

    def __init__(self, parent):
        QListView.__init__(self, parent)

        self.resultListModel = QStandardItemModel(self)
        self.setModel(self.resultListModel)
        self.selectionModel().selectionChanged.connect(self.doItemSelected)

    def doItemSelected(self, selectedtItem, idx2):
        indexes = selectedtItem.indexes()
        if len(indexes) == 1:
            item = self.resultListModel.itemFromIndex(indexes[0])
            pageId = item.text()
            self.resultSelected.emit(pageId)

    def setContents(self, linkList):
        self.resultListModel.clear()
        for item in linkList:
            resultItem = QStandardItem(item)
            resultItem.setEditable(False)
            self.resultListModel.appendRow(resultItem)
Example #9
0
class LinklistWidget(QListView):

    resultSelected = pyqtSignal(str)

    def __init__(self, parent):
        QListView.__init__(self, parent)

        self.resultListModel = QStandardItemModel(self)
        self.setModel(self.resultListModel)
        self.selectionModel().selectionChanged.connect(self.doItemSelected)

    def doItemSelected(self, selectedtItem, idx2):
        indexes = selectedtItem.indexes()
        if len(indexes) == 1:
            item = self.resultListModel.itemFromIndex(indexes[0])
            pageId = item.text()
            self.resultSelected.emit(pageId)

    def setContents(self, linkList):
        self.resultListModel.clear()
        for item in linkList:
            resultItem = QStandardItem(item)
            resultItem.setEditable(False)
            self.resultListModel.appendRow(resultItem)
Example #10
0
class ProgramInfoLogs(QWidget, Ui_ProgramInfoLogs):
    def __init__(self, context, update_main_ui_state, set_widget_enabled, is_alive,
                 show_download_wizard, set_program_callbacks_enabled):
        QWidget.__init__(self)

        self.setupUi(self)

        self.session                       = context.session
        self.script_manager                = context.script_manager
        self.program                       = context.program
        self.update_main_ui_state          = update_main_ui_state
        self.set_widget_enabled            = set_widget_enabled
        self.is_alive                      = is_alive
        self.show_download_wizard          = show_download_wizard
        self.set_program_callbacks_enabled = set_program_callbacks_enabled
        self.log_directory                 = posixpath.join(self.program.root_directory, 'log')
        self.refresh_in_progress           = False
        self.any_refresh_in_progress       = False # set from ProgramInfoMain.update_ui_state
        self.view_dialog                   = None
        self.file_icon                     = QIcon(load_pixmap('file-icon.png'))
        self.tree_logs_model               = QStandardItemModel(self)
        self.tree_logs_model_header        = ['Date/Time', 'Size']
        self.tree_logs_proxy_model         = LogsProxyModel(self)
        self.last_download_directory       = get_home_path()

        self.tree_logs_model.setHorizontalHeaderLabels(self.tree_logs_model_header)
        self.tree_logs_proxy_model.setSourceModel(self.tree_logs_model)
        self.tree_logs.setModel(self.tree_logs_proxy_model)
        self.tree_logs.setColumnWidth(0, 250)

        self.tree_logs.selectionModel().selectionChanged.connect(self.update_ui_state)
        self.tree_logs.activated.connect(self.view_activated_log)
        self.button_download_logs.clicked.connect(self.download_selected_logs)
        self.button_view_log.clicked.connect(self.view_selected_log)
        self.button_delete_logs.clicked.connect(self.delete_selected_logs)

        self.label_error.setVisible(False)

    def update_ui_state(self):
        selection_count = len(self.tree_logs.selectionModel().selectedRows())

        self.set_widget_enabled(self.button_download_logs, not self.any_refresh_in_progress and selection_count > 0)
        self.set_widget_enabled(self.button_view_log, not self.any_refresh_in_progress and selection_count == 1 and len(self.get_directly_selected_log_items()) == 1)
        self.set_widget_enabled(self.button_delete_logs, not self.any_refresh_in_progress and selection_count > 0)

    def close_all_dialogs(self):
        if self.view_dialog != None:
            self.view_dialog.close()

    def refresh_logs_done(self):
        self.refresh_in_progress = False
        self.update_main_ui_state()

    def refresh_logs(self):
        def cb_program_logs_list(result):
            okay, message = check_script_result(result, decode_stderr=True)

            if not okay:
                self.label_error.setText('<b>Error:</b> ' + html.escape(message))
                self.label_error.setVisible(True)
                self.refresh_logs_done()
                return

            try:
                # FIXME: do decompress in an async_call
                program_logs_list = json.loads(zlib.decompress(memoryview(result.stdout)).decode('utf-8'))
            except:
                program_logs_list = None

            if program_logs_list == None or not isinstance(program_logs_list, dict):
                self.label_error.setText('<b>Error:</b> Received invalid data')
                self.label_error.setVisible(True)
                self.refresh_logs_done()
                return

            self.label_error.setVisible(False)

            def create_file_size_item(size):
                item = QStandardItem(get_file_display_size(size))
                item.setData(size, USER_ROLE_SIZE)

                return item

            def update_file_size_item(item, additional_size):
                current_size = item.data(USER_ROLE_SIZE)
                new_size     = current_size + additional_size

                item.setText(get_file_display_size(new_size))
                item.setData(new_size, USER_ROLE_SIZE)

            continuous_row = None
            date_rows      = {}
            time_rows      = {}

            for file_name, file_size in program_logs_list.items():
                QApplication.processEvents()

                file_name_parts = file_name.split('_')

                if file_name_parts[0] == "continuous":
                    if len(file_name_parts) != 2:
                        continue

                    if continuous_row == None:
                        continuous_item = QStandardItem("Continuous")
                        continuous_item.setData(ITEM_TYPE_PARENT_CONT, USER_ROLE_ITEM_TYPE)

                        continuous_row = [continuous_item, create_file_size_item(0)]

                        self.tree_logs_model.appendRow(continuous_row)

                    log_item = QStandardItem(file_name_parts[1])
                    log_item.setData(self.file_icon, Qt.DecorationRole)
                    log_item.setData(file_name, USER_ROLE_FILE_NAME)
                    log_item.setData(ITEM_TYPE_LOG_FILE_CONT, USER_ROLE_ITEM_TYPE)

                    continuous_row[0].appendRow([log_item, create_file_size_item(file_size)])
                    update_file_size_item(continuous_row[1], file_size)
                else:
                    if len(file_name_parts) != 3:
                        continue

                    try:
                        timestamp = int(file_name_parts[1].split('+')[0]) // 1000000
                    except ValueError:
                        continue

                    #FIXME: fromTime_t is obsolete: https://doc.qt.io/qt-5/qdatetime-obsolete.html#toTime_t
                    date      = QDateTime.fromTime_t(timestamp).toString('yyyy-MM-dd')
                    time      = QDateTime.fromTime_t(timestamp).toString('HH:mm:ss')
                    date_time = date + 'T' + time

                    if date in date_rows:
                        date_row = date_rows[date]
                    else:
                        date_item = QStandardItem(date)
                        date_item.setData(ITEM_TYPE_PARENT_DATE, USER_ROLE_ITEM_TYPE)

                        date_row        = [date_item, create_file_size_item(0)]
                        date_rows[date] = date_row

                        self.tree_logs_model.appendRow(date_row)

                    if date_time in time_rows:
                        time_row = time_rows[date_time]
                    else:
                        time_item = QStandardItem(time)
                        time_item.setData(ITEM_TYPE_PARENT_TIME, USER_ROLE_ITEM_TYPE)

                        time_row             = [time_item, create_file_size_item(0)]
                        time_rows[date_time] = time_row

                        date_row[0].appendRow(time_row)

                    log_item = QStandardItem(file_name_parts[2])
                    log_item.setData(self.file_icon, Qt.DecorationRole)
                    log_item.setData(file_name, USER_ROLE_FILE_NAME)
                    log_item.setData(ITEM_TYPE_LOG_FILE, USER_ROLE_ITEM_TYPE)

                    time_row[0].appendRow([log_item, create_file_size_item(file_size)])
                    update_file_size_item(time_row[1], file_size)
                    update_file_size_item(date_row[1], file_size)

            self.tree_logs.header().setSortIndicator(0, Qt.DescendingOrder)
            self.refresh_logs_done()

        self.refresh_in_progress = True
        self.update_main_ui_state()

        width = self.tree_logs.columnWidth(0)
        self.tree_logs_model.clear()
        self.tree_logs_model.setHorizontalHeaderLabels(self.tree_logs_model_header)
        self.tree_logs.setColumnWidth(0, width)

        self.script_manager.execute_script('program_logs_list', cb_program_logs_list,
                                           [self.log_directory], max_length=1024*1024,
                                           decode_output_as_utf8=False)

    def get_directly_selected_log_items(self):
        selected_indexes   = self.tree_logs.selectedIndexes()
        selected_log_items = []

        for selected_index in selected_indexes:
            if selected_index.column() == 0:
                mapped_index  = self.tree_logs_proxy_model.mapToSource(selected_index)
                selected_item = self.tree_logs_model.itemFromIndex(mapped_index)
                item_type     = selected_item.data(USER_ROLE_ITEM_TYPE)

                if item_type in [ITEM_TYPE_LOG_FILE, ITEM_TYPE_LOG_FILE_CONT]:
                    selected_log_items.append(selected_item)

        return selected_log_items

    def load_log_files_for_ops(self, index_list):
        logs_download_dict = {'files': {}, 'total_download_size': 0, 'foobar':[]}

        def populate_log_download(item_list):
            item_type = item_list[0].data(USER_ROLE_ITEM_TYPE)

            if item_type == ITEM_TYPE_PARENT_CONT:
                for i in range(item_list[0].rowCount()):
                    f_size = item_list[0].child(i, 1).data(USER_ROLE_SIZE) # File size
                    file_name = item_list[0].child(i, 0).data(USER_ROLE_FILE_NAME)
                    f_path = posixpath.join(self.log_directory, file_name) # File path
                    if not f_path in logs_download_dict['files']:
                        logs_download_dict['files'][f_path] = {'size': f_size}
                        logs_download_dict['total_download_size'] += f_size
                        logs_download_dict['foobar'].append(file_name)

            elif item_type == ITEM_TYPE_PARENT_DATE:
                for i in range(item_list[0].rowCount()):
                    parent_time = item_list[0].child(i)
                    for j in range(parent_time.rowCount()):
                        f_size = parent_time.child(j, 1).data(USER_ROLE_SIZE) # File size
                        file_name = parent_time.child(j, 0).data(USER_ROLE_FILE_NAME)
                        f_path = posixpath.join(self.log_directory, file_name) # File path
                        if not f_path in logs_download_dict['files']:
                            logs_download_dict['files'][f_path] = {'size': f_size}
                            logs_download_dict['total_download_size'] += f_size
                            logs_download_dict['foobar'].append(file_name)

            elif item_type == ITEM_TYPE_PARENT_TIME:
                for i in range(item_list[0].rowCount()):
                    f_size = item_list[0].child(i, 1).data(USER_ROLE_SIZE) # File size
                    file_name = item_list[0].child(i, 0).data(USER_ROLE_FILE_NAME)
                    f_path = posixpath.join(self.log_directory, file_name) # File path
                    if not f_path in logs_download_dict['files']:
                        logs_download_dict['files'][f_path] = {'size': f_size}
                        logs_download_dict['total_download_size'] += f_size
                        logs_download_dict['foobar'].append(file_name)

            elif item_type in [ITEM_TYPE_LOG_FILE, ITEM_TYPE_LOG_FILE_CONT]:
                f_size = item_list[1].data(USER_ROLE_SIZE) # File size
                file_name = item_list[0].data(USER_ROLE_FILE_NAME)
                f_path = posixpath.join(self.log_directory, file_name) # File path
                if not f_path in logs_download_dict['files']:
                    logs_download_dict['files'][f_path] = {'size': f_size}
                    logs_download_dict['total_download_size'] += f_size
                    logs_download_dict['foobar'].append(file_name)

        index_rows = []

        for index in index_list:
            if index.column() == 0:
                index_rows.append([index,
                                   index.sibling(index.row(), 1)])

        for index_row in index_rows:
            item_list = []
            for index in index_row:
                item = self.tree_logs_model.itemFromIndex(self.tree_logs_proxy_model.mapToSource(index))
                item_list.append(item)
            populate_log_download(item_list)

        return logs_download_dict

    def download_selected_logs(self):
        index_list = self.tree_logs.selectedIndexes()

        if len(index_list) == 0:
            return

        log_files_to_download = self.load_log_files_for_ops(index_list)

        if len(log_files_to_download['foobar']) == 0:
            return

        download_directory = get_existing_directory(get_main_window(), 'Download Logs',
                                                    self.last_download_directory)

        if len(download_directory) == 0:
            return

        self.last_download_directory = download_directory

        downloads = []

        for file_name in log_files_to_download['foobar']:
            downloads.append(Download(file_name, file_name))

        self.show_download_wizard('logs', download_directory, downloads)

    def view_activated_log(self, index):
        if index.column() == 0 and not self.any_refresh_in_progress:
            mapped_index = self.tree_logs_proxy_model.mapToSource(index)
            item         = self.tree_logs_model.itemFromIndex(mapped_index)
            item_type    = item.data(USER_ROLE_ITEM_TYPE)

            if item_type in [ITEM_TYPE_LOG_FILE, ITEM_TYPE_LOG_FILE_CONT]:
                self.view_log(item)

    def view_selected_log(self):
        selection_count = len(self.tree_logs.selectionModel().selectedRows())

        if selection_count != 1:
            return

        selected_log_items = self.get_directly_selected_log_items()

        if len(selected_log_items) != 1:
            return

        self.view_log(selected_log_items[0])

    def view_log(self, item):
        file_name = posixpath.join(self.log_directory, item.data(USER_ROLE_FILE_NAME))

        self.set_program_callbacks_enabled(False)

        self.view_dialog = ProgramInfoLogsView(self, self.session, file_name)
        self.view_dialog.exec_()
        self.view_dialog = None

        if self.is_alive():
            self.set_program_callbacks_enabled(True)

    # FIXME: make this work like delete_selected_files
    def delete_selected_logs(self):
        button = QMessageBox.question(get_main_window(), 'Delete Logs',
                                      'Irreversibly deleting selected logs.',
                                      QMessageBox.Ok, QMessageBox.Cancel)

        if not self.is_alive() or button != QMessageBox.Ok:
            return

        def cb_delete(result):
            self.refresh_logs()
            report_script_result(result, 'Delete Logs Error', 'Could not delete selected logs')

        index_list = self.tree_logs.selectedIndexes()

        if not index_list:
            return

        log_files_to_delete = self.load_log_files_for_ops(index_list)

        if not log_files_to_delete:
            return

        file_list = []

        for f_path in log_files_to_delete['files']:
            file_list.append(f_path)

        if len(file_list) > 0:
            self.script_manager.execute_script('delete', cb_delete,
                                               [json.dumps(file_list), json.dumps([])],
                                               execute_as_user=True)
Example #11
0
class QWTable(QTableView):
    def __init__(self, parent=None):
        QTableView.__init__(self, parent)
        self._name = self.__class__.__name__

        icon.set_icons()

        self.is_connected_item_changed = False

        self.model = QStandardItemModel()
        self.set_selection_mode()
        self.fill_table_model()  # defines self.model
        self.setModel(self.model)

        self.connect_item_selected(self.on_item_selected)
        self.clicked.connect(self.on_click)
        self.doubleClicked.connect(self.on_double_click)
        #self.connect_item_changed(self.on_item_changed)

        self.set_style()

    #def __del__(self):
    #    QTableView.__del__(self) - it does not have __del__

    def set_selection_mode(self, smode=QAbstractItemView.ExtendedSelection):
        logger.debug('Set selection mode: %s' % smode)
        self.setSelectionMode(smode)

    def connect_item_changed(self, recipient):
        self.model.itemChanged.connect(recipient)
        self.is_connected_item_changed = True

    def disconnect_item_changed(self, recipient):
        if self.is_connected_item_changed:
            self.model.itemChanged.disconnect(recipient)
            self.is_connected_item_changed = False

    def connect_item_selected(self, recipient):
        self.selectionModel().currentChanged[QModelIndex,
                                             QModelIndex].connect(recipient)

    def disconnect_item_selected(self, recipient):
        self.selectionModel().currentChanged[QModelIndex,
                                             QModelIndex].disconnect(recipient)

    def set_style(self):
        self.setStyleSheet("QTableView::item:hover{background-color:#00FFAA;}")

    def fill_table_model(self):
        self.clear_model()
        self.model.setHorizontalHeaderLabels(
            ['col0', 'col1', 'col2', 'col3', 'col4'])
        self.model.setVerticalHeaderLabels(['row0', 'row1', 'row2', 'row3'])
        for row in range(0, 4):
            for col in range(0, 6):
                item = QStandardItem("itemA %d %d" % (row, col))
                item.setIcon(icon.icon_table)
                item.setCheckable(True)
                self.model.setItem(row, col, item)
                if col == 2: item.setIcon(icon.icon_folder_closed)
                if col == 3: item.setText('Some text')
                #self.model.appendRow(item)

    def clear_model(self):
        rows, cols = self.model.rowCount(), self.model.columnCount()
        self.model.removeRows(0, rows)
        self.model.removeColumns(0, cols)

    def selected_indexes(self):
        return self.selectedIndexes()

    def selected_items(self):
        indexes = self.selectedIndexes()
        return [self.model.itemFromIndex(i) for i in self.selectedIndexes()]

    def getFullNameFromItem(self, item):
        #item = self.model.itemFromIndex(ind)
        ind = self.model.indexFromItem(item)
        return self.getFullNameFromIndex(ind)

    def getFullNameFromIndex(self, ind):
        item = self.model.itemFromIndex(ind)
        if item is None: return None
        self._full_name = item.text()
        self._getFullName(ind)
        return self._full_name

    def _getFullName(self, ind):
        ind_par = self.model.parent(ind)
        if (ind_par.column() == -1):
            item = self.model.itemFromIndex(ind)
            self.full_name = '/' + self._full_name
            #logger.debug('Item full name:' + self._full_name)
            return self._full_name
        else:
            item_par = self.model.itemFromIndex(ind_par)
            self._full_name = item_par.text() + '/' + self._full_name
            self._getFullName(ind_par)

    def closeEvent(self, event):  # if the x is clicked
        logger.debug('closeEvent')

    def on_click(self, index):
        item = self.model.itemFromIndex(index)
        msg = 'on_click: item in row:%02d text: %s' % (index.row(),
                                                       item.text())
        logger.debug(msg)

    def on_double_click(self, index):
        item = self.model.itemFromIndex(index)
        msg = 'on_double_click: item in row:%02d text: %s' % (index.row(),
                                                              item.text())
        logger.debug(msg)

    def on_item_selected(self, ind_sel, ind_desel):
        item = self.model.itemFromIndex(ind_sel)
        logger.debug('on_item_selected: "%s" is selected' %
                     (item.text() if item is not None else None))

    def on_item_changed(self, item):
        state = ['UNCHECKED', 'TRISTATE', 'CHECKED'][item.checkState()]
        logger.debug('abstract on_item_changed: "%s" at state %s' %
                     (self.getFullNameFromItem(item), state))

    def process_selected_items(self):
        selitems = self.selected_items()
        msg = '%d Selected items:' % len(selitems)
        for i in selitems:
            msg += '\n  %s' % i.text()
        logger.info(msg)

    def key_usage(self):
        return 'Keys:'\
               '\n  ESC - exit'\
               '\n  S - show selected items'\
               '\n'

    def keyPressEvent(self, e):
        logger.info('keyPressEvent, key=', e.key())
        if e.key() == Qt.Key_Escape:
            self.close()

        elif e.key() == Qt.Key_S:
            self.process_selected_items()

        else:
            logger.info(self.key_usage())
Example #12
0
class AttrsWidget(QObject):

    error = pyqtSignal(Exception)
    attr_written = pyqtSignal(ua.AttributeIds, ua.DataValue)

    def __init__(self, view, show_timestamps=True):
        QObject.__init__(self, view)
        self.view = view
        self._timestamps = show_timestamps
        delegate = MyDelegate(self.view, self)
        delegate.error.connect(self.error.emit)
        delegate.attr_written.connect(self.attr_written.emit)
        self.settings = QSettings()
        self.view.setItemDelegate(delegate)
        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['Attribute', 'Value', 'DataType'])
        state = self.settings.value("WindowState/attrs_widget_state", None)
        if state is not None:
            self.view.header().restoreState(state)
        self.view.setModel(self.model)
        self.current_node = None
        self.view.header().setSectionResizeMode(0)
        self.view.header().setStretchLastSection(True)
        self.view.expanded.connect(self._item_expanded)
        self.view.collapsed.connect(self._item_collapsed)
        self.view.setEditTriggers(QAbstractItemView.DoubleClicked)

        # Context menu
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        copyaction = QAction("&Copy Value", self.model)
        copyaction.triggered.connect(self._copy_value)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(copyaction)

    def save_state(self):
        self.settings.setValue("WindowState/attrs_widget_state", self.view.header().saveState())

    def _item_expanded(self, idx):
        if not idx.parent().isValid():
            # only for value attributes which a re childs
            # maybe add more tests
            return
        it = self.model.itemFromIndex(idx.sibling(0, 1))
        it.setText("")

    def _item_collapsed(self, idx):
        it = self.model.itemFromIndex(idx.sibling(0, 1))
        data = it.data(Qt.UserRole)
        it.setText(val_to_string(data.value))

    def showContextMenu(self, position):
        item = self.get_current_item()
        if item:
            self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))

    def get_current_item(self, col_idx=0):
        idx = self.view.currentIndex()
        idx = idx.siblingAtColumn(col_idx)
        return self.model.itemFromIndex(idx)

    def _copy_value(self, position):
        it = self.get_current_item(1)
        if it:
            QApplication.clipboard().setText(it.text())

    def clear(self):
        # remove all rows but not header!!
        self.model.removeRows(0, self.model.rowCount())

    def reload(self):
        self.show_attrs(self.current_node)

    def show_attrs(self, node):
        self.current_node = node
        self.clear()
        if self.current_node:
            self._show_attrs()
        self.view.expandToDepth(0)

    def _show_attrs(self):
        attrs = self.get_all_attrs()
        for attr, dv in attrs:
            try:
                # try/except to show as many attributes as possible
                if attr == ua.AttributeIds.Value:
                    self._show_value_attr(attr, dv)
                else:
                    self._show_attr(attr, dv)
            except Exception as ex:
                logger.exception("Exception while displaying attribute %s with value %s for node %s", attr, dv, self.current_node)
                self.error.emit(ex)

    def _show_attr(self, attr, dv):
        if attr == ua.AttributeIds.DataType:
            # FIXME: Could query for browsename here, it does not cost much
            string = data_type_to_string(dv.Value.Value)
        elif attr in (ua.AttributeIds.AccessLevel,
                      ua.AttributeIds.UserAccessLevel,
                      ua.AttributeIds.WriteMask,
                      ua.AttributeIds.UserWriteMask,
                      ua.AttributeIds.EventNotifier):
            string = enum_to_string(attr, dv.Value.Value)
        else:
            string = val_to_string(dv.Value.Value)
        name_item = QStandardItem(attr.name)
        vitem = QStandardItem(string)
        vitem.setData(AttributeData(attr, dv.Value.Value, dv.Value.VariantType), Qt.UserRole)
        self.model.appendRow([name_item, vitem, QStandardItem(dv.Value.VariantType.name)])

    def _show_value_attr(self, attr, dv):
        name_item = QStandardItem("Value")
        vitem = QStandardItem()
        items = self._show_val(name_item, None, "Value", dv.Value.Value, dv.Value.VariantType)
        items[1].setData(AttributeData(attr, dv.Value.Value, dv.Value.VariantType), Qt.UserRole)
        row = [name_item, vitem, QStandardItem(dv.Value.VariantType.name)]
        self.model.appendRow(row)
        self._show_timestamps(name_item, dv)

    def _show_val(self, parent, obj, name, val, vtype):
        name_item = QStandardItem(name)
        vitem = QStandardItem()
        vitem.setText(val_to_string(val))
        vitem.setData(MemberData(obj, name, val, vtype), Qt.UserRole)
        row = [name_item, vitem, QStandardItem(vtype.name)]
        # if we have a list or extension object we display children
        if isinstance(val, list):
            row[2].setText("List of " + vtype.name)
            self._show_list(name_item, val, vtype)
        elif vtype == ua.VariantType.ExtensionObject:
            self._show_ext_obj(name_item, val)
        parent.appendRow(row)
        return row

    def _show_list(self, parent, mylist, vtype):
        for idx, val in enumerate(mylist):
            name_item = QStandardItem(str(idx))
            vitem = QStandardItem()
            vitem.setText(val_to_string(val))
            vitem.setData(ListData(mylist, idx, val, vtype), Qt.UserRole)
            row = [name_item, vitem, QStandardItem(vtype.name)]
            parent.appendRow(row)
            if vtype == ua.VariantType.ExtensionObject:
                self._show_ext_obj(name_item, val)
    
    def refresh_list(self, parent, mylist, vtype):
        while parent.hasChildren():
            self.model.removeRow(0, parent.index())
        self._show_list(parent, mylist, vtype)

    def _show_ext_obj(self, item, val):
        item.setText(item.text() + ": " + val.__class__.__name__)
        for att_name, att_type in val.ua_types:
            member_val = getattr(val, att_name)
            if att_type.startswith("ListOf"):
                att_type = att_type[6:]
            attr = getattr(ua.VariantType, att_type)
            self._show_val(item, val, att_name, member_val, attr)

    def _show_timestamps(self, item, dv):
        #while item.hasChildren():
            #self.model.removeRow(0, item.index())
        string = val_to_string(dv.ServerTimestamp)
        item.appendRow([QStandardItem("Server Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])
        string = val_to_string(dv.SourceTimestamp)
        item.appendRow([QStandardItem("Source Timestamp"), QStandardItem(string), QStandardItem(ua.VariantType.DateTime.name)])


    def get_all_attrs(self):
        attrs = [attr for attr in ua.AttributeIds]
        dvs = self.current_node.get_attributes(attrs)
        res = []
        for idx, dv in enumerate(dvs):
            if dv.StatusCode.is_good():
                res.append((attrs[idx], dv))
        res.sort(key=lambda x: x[0].name)
        return res
Example #13
0
class QmyMainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.setCentralWidget(self.ui.qSplitter)
        self.__ColCount = 6
        self.itemModel = QStandardItemModel(5, self.__ColCount, self)
        self.selectionModel = QItemSelectionModel(self.itemModel)
        self.selectionModel.currentChanged.connect(self.do_curChanged)

        self.__lastColumnTitle = "测井取样"
        self.__lastColumnFlags = (Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
                                  | Qt.ItemIsEnabled)

        self.ui.qTableView.setModel(self.itemModel)
        self.ui.qTableView.setSelectionModel(self.selectionModel)

        oneOrMore = QAbstractItemView.ExtendedSelection
        self.ui.qTableView.setSelectionMode(oneOrMore)

        itemOrRow = QAbstractItemView.SelectItems
        self.ui.qTableView.setSelectionBehavior(itemOrRow)

        self.ui.qTableView.verticalHeader().setDefaultSectionSize(22)
        self.ui.qTableView.setAlternatingRowColors(True)

        self.ui.qTableView.setEnabled(False)

        self.qLabel1 = QLabel("当前单元格:", self)
        self.qLabel1.setMinimumWidth(180)
        self.qLabel2 = QLabel("单元格内容:", self)
        self.qLabel2.setMinimumWidth(150)
        self.qLabel3 = QLabel("当前文件:", self)
        self.ui.qStatusBar.addWidget(self.qLabel1)
        self.ui.qStatusBar.addWidget(self.qLabel2)
        self.ui.qStatusBar.addPermanentWidget(self.qLabel3)

    def __iniModelFromStringList(self, allLines):
        rowCnt = len(allLines)
        self.itemModel.setRowCount(rowCnt - 1)
        headerText = allLines[0].strip()
        headerList = headerText.split("\t")
        self.itemModel.setHorizontalHeaderLabels(headerList)
        self.__lastColumnTitle = headerList[len(headerList) - 1]
        lastColNo = self.__ColCount - 1
        for i in range(rowCnt - 1):
            lineText = allLines[i + 1].strip()
            strList = lineText.split("\t")
            for j in range(self.__ColCount - 1):
                item = QStandardItem(strList[j])
                self.itemModel.setItem(i, j, item)

            item = QStandardItem(self.__lastColumnTitle)
            item.setFlags(self.__lastColumnFlags)
            item.setCheckable(True)
            if (strList[lastColNo] == "0"):
                item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Checked)
            self.itemModel.setItem(i, lastColNo, item)

    def __setCellAlignment(self, align=Qt.AlignHCenter):
        if (not self.selectionModel.hasSelection()):
            return
        selectedIndex = self.selectionModel.selectedIndexes()
        count = len(selectedIndex)
        for i in range(count):
            index = selectedIndex[i]
            item = self.itemModel.itemFromIndex(index)
            item.setTextAlignment(align)

    @pyqtSlot()
    def on_qAction1_triggered(self):  # 打开文件
        curPath = os.getcwd()
        filename, flt = QFileDialog.getOpenFileName(
            self, "打开一个文件", curPath, "井斜数据文件(*.txt);;所有文件(*.*)")
        if (filename == ""):
            return
        self.qLabel3.setText("当前文件:" + filename)
        self.ui.qPlainTextEdit.clear()
        aFile = open(filename, 'r')
        allLines = aFile.readlines()
        aFile.close()

        for strLine in allLines:
            self.ui.qPlainTextEdit.appendPlainText(strLine.strip())

        self.__iniModelFromStringList(allLines)
        self.ui.qTableView.setEnabled(True)
        self.ui.qAction2.setEnabled(True)
        self.ui.qAction3.setEnabled(True)
        self.ui.qAction4.setEnabled(True)
        self.ui.qAction5.setEnabled(True)
        self.ui.qAction6.setEnabled(True)

    @pyqtSlot()
    def on_qAction2_triggered(self):  # 另存文件
        curPath = os.getcwd()
        filename, flt = QFileDialog.getSaveFileName(
            self, "保存文件", curPath, "井斜数据文件(*.txt);;所有文件(*.*)")
        if (filename == ""):
            return
        self.on_qAction3_triggered()
        aFile = open(filename, "w")
        aFile.write(self.ui.qPlainTextEdit.toPlainText())
        aFile.close()

    @pyqtSlot()
    def on_qAction4_triggered(self):  # 添加行
        itemList = []
        for i in range(self.__ColCount - 1):
            item = QStandardItem("0")
            itemList.append(item)

        item = QStandardItem(self.__lastColumnTitle)
        item.setCheckable(True)
        item.setFlags(self.__lastColumnFlags)
        itemList.append(item)

        self.itemModel.appendRow(itemList)
        curIndex = self.itemModel.index(self.itemModel.rowCount() - 1, 0)
        self.selectionModel.clearSelection()
        self.selectionModel.setCurrentIndex(curIndex,
                                            QItemSelectionModel.Select)

    @pyqtSlot()
    def on_qAction5_triggered(self):  # 插入行
        itemlist = []
        for i in range(self.__ColCount - 1):
            item = QStandardItem("0")
            itemlist.append(item)

        item = QStandardItem(self.__lastColumnTitle)
        item.setFlags(self.__lastColumnFlags)
        item.setCheckable(True)
        item.setCheckState(Qt.Checked)
        itemlist.append(item)

        curIndex = self.selectionModel.currentIndex()
        self.itemModel.insertRow(curIndex.row(), itemlist)
        self.selectionModel.clearSelection()
        self.selectionModel.setCurrentIndex(curIndex,
                                            QItemSelectionModel.Select)

    @pyqtSlot()
    def on_qAction6_triggered(self):  # 删除行
        curIndex = self.selectionModel.currentIndex()
        self.itemModel.removeRow(curIndex.row())

    @pyqtSlot()
    def on_qAction7_triggered(self):  # 居左
        self.__setCellAlignment(Qt.AlignLeft | Qt.AlignVCenter)

    @pyqtSlot()
    def on_qAction8_triggered(self):  # 居中
        self.__setCellAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

    @pyqtSlot()
    def on_qAction9_triggered(self):  # 居右
        self.__setCellAlignment(Qt.AlignRight | Qt.AlignVCenter)

    @pyqtSlot(bool)
    def on_qAction10_triggered(self, checked):  # 粗体
        if (not self.selectionModel.hasSelection()):
            return
        selectedIndex = self.selectionModel.selectedIndexes()
        count = len(selectedIndex)
        for i in range(count):
            index = selectedIndex[i]
            item = self.itemModel.itemFromIndex(index)
            font = item.font()
            font.setBold(checked)
            item.setFont(font)

    @pyqtSlot()
    def on_qAction3_triggered(self):  # 模型数据
        self.ui.qPlainTextEdit.clear()
        lineStr = ""
        for i in range(self.itemModel.columnCount() - 1):
            item = self.itemModel.horizontalHeaderItem(i)
            lineStr = lineStr + item.text() + "\t"
        item = self.itemModel.horizontalHeaderItem(self.__ColCount - 1)
        lineStr = lineStr + item.text()
        self.ui.qPlainTextEdit.appendPlainText(lineStr)

        for i in range(self.itemModel.rowCount()):
            lineStr = ""
            for j in range(self.itemModel.columnCount() - 1):
                item = self.itemModel.item(i, j)
                lineStr = lineStr + item.text() + "\t"
            item = self.itemModel.item(i, self.__ColCount - 1)
            if (item.checkState() == Qt.Checked):
                lineStr = lineStr + "1"
            else:
                lineStr = lineStr + "0"
            self.ui.qPlainTextEdit.appendPlainText(lineStr)

    def do_curChanged(self, current, previous):
        if (current != None):
            text = "当前单元格:%d行,%d列" % (current.row(), current.column())
            self.qLabel1.setText(text)
            item = self.itemModel.itemFromIndex(current)
            self.qLabel2.setText("单元格内容:" + item.text())
            font = item.font()
            self.ui.qAction10.setChecked(font.bold())
Example #14
0
class BacktraceWidget(DwarfListView):

    onShowMemoryRequest = pyqtSignal(list, name='onShowMemoryRequest')

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

        self._app_window.dwarf.onBackTrace.connect(self.set_backtrace)

        self._model = QStandardItemModel(0, 2)
        self._model.setHeaderData(0, Qt.Horizontal, 'Address')
        self._model.setHeaderData(1, Qt.Horizontal, 'Symbol')
        self.setModel(self._model)

        self.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        self.doubleClicked.connect(self._item_double_clicked)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._on_context_menu)
        self._mode = 'native'

    def set_backtrace(self, bt):
        if 'type' not in bt:
            return

        if 'bt' not in bt:
            return

        self.clear()

        if bt['type'] == 'native':
            self._mode = 'native'
            self._model.setHeaderData(0, Qt.Horizontal, 'Address')
            self._model.setHeaderData(1, Qt.Horizontal, 'Symbol')

            bt = bt['bt']

            for a in bt:
                addr = a['address']
                if self.uppercase_hex:
                    addr = addr.upper().replace('0X', '0x')

                addr_item = QStandardItem()
                addr_item.setText(addr)
                addr_item.setForeground(Qt.red)

                name = a['name']
                if name is None:
                    name = '-'

                self._model.appendRow([addr_item, QStandardItem(name)])

        elif bt['type'] == 'java':
            self._mode = 'java'
            self._model.setHeaderData(0, Qt.Horizontal, 'Method')
            self._model.setHeaderData(1, Qt.Horizontal, 'Source')

            bt = bt['bt']
            parts = bt.split('\n')
            for i in range(0, len(parts)):
                if i == 0:
                    continue
                p = parts[i].replace('\t', '')
                p = p.split('(')
                if len(p) != 2:
                    continue

                self._model.appendRow([
                    QStandardItem(p[0]),
                    QStandardItem(p[1].replace(')', ''))
                ])

    def _item_double_clicked(self, model_index):
        row = self._model.itemFromIndex(model_index).row()
        if row != -1:
            if self._mode == 'native':
                self.onShowMemoryRequest.emit(
                    ['bt', self._model.item(row, 0).text()])

    def _on_context_menu(self, pos):
        index = self.indexAt(pos).row()
        glbl_pt = self.mapToGlobal(pos)
        context_menu = QMenu(self)
        if index != -1:
            if self._mode == 'native':
                addr_item = self.model().item(index, 0).text()
                symbol_item = self.model().item(index, 1).text()
                # show contextmenu
                context_menu.addAction(
                    'Jump to {0}'.format(addr_item),
                    lambda: self.onShowMemoryRequest.emit(['bt', addr_item]))
                context_menu.addSeparator()
                context_menu.addAction(
                    'Copy Address',
                    lambda: utils.copy_hex_to_clipboard(addr_item))
                if symbol_item and symbol_item != '-':
                    context_menu.addAction(
                        'Copy Symbol',
                        lambda: utils.copy_str_to_clipboard(symbol_item))
            elif self._mode == 'java':
                method_item = self.model().item(index, 0).text()
                if method_item.startswith('at '):
                    method_item = method_item.replace('at ', '')

                source_item = self.model().item(index, 1).text()
                if ':' in source_item:
                    source_item = source_item.split(':')[0]
                # show contextmenu
                # context_menu.addAction('Jump to', lambda: self._app_window.jump_to_address(addr_item.text()))
                # context_menu.addSeparator()
                # TODO: add jumpto java
                context_menu.addAction(
                    'Copy Method',
                    lambda: utils.copy_str_to_clipboard(method_item))
                context_menu.addAction(
                    'Copy Source',
                    lambda: utils.copy_str_to_clipboard(source_item))

            context_menu.exec_(glbl_pt)
Example #15
0
class RangesPanel(DwarfListView):
    """ RangesPanel

        Signals:
            onItemDoubleClicked(str) - only fired when prot has +r
            onDumpBinary([ptr, size#int]) - MenuItem DumpBinary
            onAddWatchpoint(str) - MenuItem AddWatchpoint
    """

    onItemDoubleClicked = pyqtSignal(str, name='onItemDoubleClicked')
    onDumpBinary = pyqtSignal(list, name='onDumpBinary')
    onAddWatchpoint = pyqtSignal(str, name='onAddWatchpoint')

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

        if self._app_window.dwarf is None:
            print('RangesPanel created before Dwarf exists')
            return

        # connect to dwarf
        self._app_window.dwarf.onSetRanges.connect(self.set_ranges)

        self._uppercase_hex = True

        self._ranges_model = QStandardItemModel(0, 6)
        self._ranges_model.setHeaderData(0, Qt.Horizontal, 'Address')
        self._ranges_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(1, Qt.Horizontal, 'Size')
        self._ranges_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(2, Qt.Horizontal, 'Protection')
        self._ranges_model.setHeaderData(2, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(3, Qt.Horizontal, 'FileOffset')
        self._ranges_model.setHeaderData(3, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(4, Qt.Horizontal, 'FileSize')
        self._ranges_model.setHeaderData(4, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(5, Qt.Horizontal, 'FilePath')

        self.setHeaderHidden(False)
        self.setAutoFillBackground(True)
        self.setEditTriggers(self.NoEditTriggers)
        self.setRootIsDecorated(False)
        self.doubleClicked.connect(self._range_dblclicked)
        self.setModel(self._ranges_model)
        # self.setSortingEnabled(True)
        self.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        self.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
        self.header().setSectionResizeMode(3, QHeaderView.ResizeToContents)
        self.header().setSectionResizeMode(4, QHeaderView.ResizeToContents)

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._on_contextmenu)

        self.update_ranges()

    # ************************************************************************
    # **************************** Properties ********************************
    # ************************************************************************
    @property
    def uppercase_hex(self):
        """ Addresses displayed lower/upper-case
        """
        return self._uppercase_hex

    @uppercase_hex.setter
    def uppercase_hex(self, value):
        """ Addresses displayed lower/upper-case
            value - bool or str
                    'upper', 'lower'
        """
        if isinstance(value, bool):
            self._uppercase_hex = value
        elif isinstance(value, str):
            self._uppercase_hex = (value == 'upper')

    # ************************************************************************
    # **************************** Functions *********************************
    # ************************************************************************
    def set_ranges(self, ranges):
        """ Fills Rangelist with Data
        """
        if isinstance(ranges, list):
            self._ranges_model.removeRows(0, self._ranges_model.rowCount())
            for range_entry in ranges:
                # create items to add
                if self._uppercase_hex:
                    str_frmt = '0x{0:X}'
                else:
                    str_frmt = '0x{0:x}'

                addr = QStandardItem()
                addr.setTextAlignment(Qt.AlignCenter)
                addr.setText(str_frmt.format(int(range_entry['base'], 16)))

                size = QStandardItem()
                size.setTextAlignment(Qt.AlignRight)
                size.setText("{0:,d}".format(int(range_entry['size'])))

                protection = QStandardItem()
                protection.setTextAlignment(Qt.AlignCenter)
                protection.setText(range_entry['protection'])

                file_path = None
                file_addr = None
                file_size = None

                if len(range_entry) > 3:
                    if range_entry['file']['path']:
                        file_path = QStandardItem()
                        file_path.setText(range_entry['file']['path'])

                    if range_entry['file']['offset']:
                        file_addr = QStandardItem()
                        file_addr.setTextAlignment(Qt.AlignCenter)
                        file_addr.setText(
                            str_frmt.format(range_entry['file']['offset']))

                    if range_entry['file']['size']:
                        file_size = QStandardItem()
                        file_size.setTextAlignment(Qt.AlignRight)
                        file_size.setText("{0:,d}".format(
                            int(range_entry['file']['size'])))

                self._ranges_model.appendRow(
                    [addr, size, protection, file_addr, file_size, file_path])

            self.resizeColumnToContents(0)
            self.resizeColumnToContents(1)
            self.resizeColumnToContents(2)
            self.resizeColumnToContents(3)
            self.resizeColumnToContents(4)

    def update_ranges(self):
        """ DwarfApiCall updateRanges
        """
        self._app_window.dwarf.dwarf_api('updateRanges')

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    def _on_contextmenu(self, pos):
        """ ContextMenu
        """
        index = self.indexAt(pos).row()
        glbl_pt = self.mapToGlobal(pos)
        context_menu = QMenu(self)
        if index != -1:
            mem_prot = self._ranges_model.item(index, 2).text()
            # is readable
            if 'r' in mem_prot:
                context_menu.addAction(
                    'Dump Binary', lambda: self._on_dumprange(
                        self._ranges_model.item(index, 0).text(),
                        self._ranges_model.item(index, 1).text()))
                context_menu.addSeparator()

            context_menu.addAction(
                'Add Watchpoint', lambda: self._on_addwatchpoint(
                    self._ranges_model.item(index, 0).text()))

            context_menu.addAction(
                'Copy address', lambda: utils.copy_hex_to_clipboard(
                    self._ranges_model.item(index, 0).text()))
            context_menu.addSeparator()

            if self._ranges_model.item(index, 5):
                file_path = self._ranges_model.item(index, 5).text()
                if file_path:
                    context_menu.addAction(
                        'Copy Path', lambda: utils.copy_str_to_clipboard(
                            file_path))
                    context_menu.addSeparator()
                    if self._app_window.dwarf._platform == 'linux':
                        context_menu.addAction(
                            'Show ELF Info', lambda: self._on_parse_elf(
                                file_path))
                        context_menu.addSeparator()

            if self.search_enabled:
                context_menu.addSeparator()
                context_menu.addAction('Search', self._on_cm_search)
                context_menu.addSeparator()

        context_menu.addAction('Refresh', self.update_ranges)
        context_menu.exec_(glbl_pt)

    def _range_dblclicked(self, model_index):
        """ RangeItem DoubleClicked
        """
        row = self._ranges_model.itemFromIndex(model_index).row()
        if row != -1:
            mem_prot = self._ranges_model.item(row, 2).text()
            # not readable?
            if 'r' in mem_prot:
                ptr = self._ranges_model.item(row, 0).text()
                self.onItemDoubleClicked.emit(ptr)

    def _on_dumprange(self, ptr, size):
        """ MenuItem DumpBinary
        """
        if isinstance(ptr, int):
            str_fmt = '0x{0:X}'
            if not self.uppercase_hex:
                str_fmt = '0x{0:x}'
            ptr = str_fmt.format(ptr)

        size = size.replace(',', '')
        self.onDumpBinary.emit([ptr, size])

    def _on_addwatchpoint(self, ptr):
        """ MenuItem AddWatchpoint
        """
        if isinstance(ptr, int):
            str_fmt = '0x{0:X}'
            if not self.uppercase_hex:
                str_fmt = '0x{0:x}'
            ptr = str_fmt.format(ptr)

        if not self._app_window.dwarf.dwarf_api('isAddressWatched', int(
                ptr, 16)):
            self.onAddWatchpoint.emit(ptr)

    def _on_parse_elf(self, elf_path):
        from dwarf_debugger.ui.dialogs.elf_info_dlg import ElfInfo
        parsed_infos = self._app_window.dwarf.dwarf_api('parseElf', elf_path)
        if parsed_infos:
            elf_dlg = ElfInfo(self._app_window, elf_path)
            elf_dlg.onShowMemoryRequest.connect(self.onItemDoubleClicked)
            elf_dlg.set_parsed_data(parsed_infos)
            elf_dlg.show()
Example #16
0
class EmulatorPanel(QWidget):
    def __init__(self, app, *__args):
        super().__init__(*__args)

        self.app = app
        self.emulator = self.app.dwarf.emulator
        self.until_address = 0

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        self._toolbar = QToolBar()
        self._toolbar.addAction('Start', self.handle_start)
        self._toolbar.addAction('Step', self.handle_step)
        self._toolbar.addAction('Stop', self.handle_stop)
        self._toolbar.addAction('Clean', self.handle_clean)
        self._toolbar.addAction('Options', self.handle_options)

        layout.addWidget(self._toolbar)

        self.tabs = QTabWidget()
        self.assembly = DisassemblyView(self.app)
        self.assembly.display_jumps = False
        self.assembly.follow_jumps = False
        self.memory_table = MemoryPanel(self.app)
        self.memory_table._read_only = True
        self.tabs.addTab(self.assembly, 'Code')
        self.tabs.addTab(self.memory_table, 'Memory')

        layout.addWidget(self.tabs)

        h_box = QHBoxLayout()
        self.ranges_list = DwarfListView(self.app)
        self.ranges_list.doubleClicked.connect(self.ranges_item_double_clicked)
        self._ranges_model = QStandardItemModel(0, 2)
        self._ranges_model.setHeaderData(0, Qt.Horizontal, 'Memory')
        self._ranges_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(1, Qt.Horizontal, 'Size')
        self.ranges_list.setModel(self._ranges_model)

        self._access_list = DwarfListView(self.app)
        self._access_list.doubleClicked.connect(
            self.access_item_double_clicked)
        self._access_model = QStandardItemModel(0, 3)
        self._access_model.setHeaderData(0, Qt.Horizontal, 'Address')
        self._access_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._access_model.setHeaderData(1, Qt.Horizontal, 'Access')
        self._access_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._access_model.setHeaderData(2, Qt.Horizontal, 'Value')
        self._access_list.setModel(self._access_model)
        h_box.addWidget(self.ranges_list)
        h_box.addWidget(self._access_list)

        layout.addLayout(h_box)
        self.setLayout(layout)

        self.console = self.app.console.get_emu_console()

        self.emulator.onEmulatorStart.connect(self.on_emulator_start)
        self.emulator.onEmulatorStop.connect(self.on_emulator_stop)
        # self.emulator.onEmulatorStep.connect(self.on_emulator_step)
        self.emulator.onEmulatorHook.connect(self.on_emulator_hook)
        self.emulator.onEmulatorMemoryHook.connect(
            self.on_emulator_memory_hook)
        self.emulator.onEmulatorMemoryRangeMapped.connect(
            self.on_emulator_memory_range_mapped)
        self.emulator.onEmulatorLog.connect(self.on_emulator_log)

        self._require_register_result = None
        self._last_instruction_address = 0

    def resizeEvent(self, event):
        self.ranges_list.setFixedHeight((self.height() / 100) * 25)
        self.ranges_list.setFixedWidth((self.width() / 100) * 30)
        self._access_list.setFixedHeight((self.height() / 100) * 25)
        return super().resizeEvent(event)

    def handle_clean(self):
        self.ranges_list.clear()
        self._access_list.clear()
        self.assembly._lines.clear()
        self.assembly.viewport().update()
        # self.memory_table.setRowCount(0)
        self.console.clear()
        self.emulator.clean()

    def handle_options(self):
        EmulatorConfigsDialog.show_dialog(self.app.dwarf)

    def handle_start(self):
        ph = ''
        if self.until_address > 0:
            ph = hex(self.until_address)
        address, inp = InputDialog.input_pointer(
            self.app, input_content=ph, hint='pointer to last instruction')
        if address > 0:
            self.until_address = address
            self.emulator.emulate(self.until_address)
            # if err > 0:
            #    self.until_address = 0
            #    self.console.log('cannot start emulator. err: %d' % err)
            #    return

    def handle_step(self):
        try:
            result = self.emulator.emulate()
            if not result:
                self.console.log('Emulation failed')
        except self.emulator.EmulatorAlreadyRunningError:
            self.console.log('Emulator already runnging')
        except self.emulator.EmulatorSetupFailedError as error:
            self.until_address = 0
            self.console.log(error)

    def handle_stop(self):
        self.emulator.stop()

    def on_emulator_hook(self, instruction):
        self.app.context_panel.set_context(0, 2, self.emulator.current_context)
        # check if the previous hook is waiting for a register result
        if self._require_register_result is not None:
            row = 1
            res = '%s = %s' % (self._require_register_result[1],
                               hex(
                                   self.emulator.uc.reg_read(
                                       self._require_register_result[0])))
            if len(self.assembly._lines) > 1:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row] is None:
                    row = 2
                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res
                # invalidate
                self._require_register_result = None

        # check if the code jumped
        self._last_instruction_address = instruction.address

        self.assembly.add_instruction(instruction)

        # add empty line if jump
        if instruction.is_jump:
            self.assembly.add_instruction(None)

        # implicit regs read are notified later through mem access
        if len(instruction.regs_read) == 0:
            if len(instruction.operands) > 0:
                for i in instruction.operands:
                    if i.type == CS_OP_REG:
                        self._require_register_result = [
                            i.value.reg,
                            instruction.reg_name(i.value.reg)
                        ]
                        break
        self.assembly.verticalScrollBar().setValue(len(self.assembly._lines))
        self.assembly.viewport().update()

    def on_emulator_log(self, log):
        self.app.console_panel.show_console_tab('emulator')
        self.console.log(log)

    def on_emulator_memory_hook(self, data):
        uc, access, address, value = data
        _address = QStandardItem()
        str_frmt = ''
        if self.ranges_list.uppercase_hex:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016X}'.format(address)
            else:
                str_frmt = '0x{0:08X}'.format(address)
        else:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016x}'.format(address)
            else:
                str_frmt = '0x{0:08x}'.format(address)
        _address.setText(str_frmt)
        _address.setTextAlignment(Qt.AlignCenter)

        _access = QStandardItem()
        if access == UC_MEM_READ:
            _access.setText('READ')
        elif access == UC_MEM_WRITE:
            _access.setText('WRITE')
        elif access == UC_MEM_FETCH:
            _access.setText('FETCH')
        _access.setTextAlignment(Qt.AlignCenter)

        _value = QStandardItem()
        _value.setText(str(value))

        self._access_model.appendRow([_address, _access, _value])

        res = None
        row = 1
        if len(self.assembly._lines) > 1:
            if self.assembly._lines[len(self.assembly._lines) - row] is None:
                row = 2
            if access == UC_MEM_READ:
                if self._require_register_result is not None:
                    res = '%s = %s' % (self._require_register_result[1],
                                       hex(value))
            else:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row].string:
                    res = '%s, %s = %s' % (self.assembly._lines[
                        len(self.assembly._lines) - row].string, hex(address),
                                           hex(value))
                else:
                    res = '%s = %s' % (hex(address), hex(value))
            if res is not None:
                # invalidate
                self._require_register_result = None

                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res

    def on_emulator_memory_range_mapped(self, data):
        address, size = data
        _address = QStandardItem()
        str_frmt = ''
        if self.ranges_list.uppercase_hex:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016X}'.format(address)
            else:
                str_frmt = '0x{0:08X}'.format(address)
        else:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016x}'.format(address)
            else:
                str_frmt = '0x{0:08x}'.format(address)
        _address.setText(str_frmt)
        _address.setTextAlignment(Qt.AlignCenter)
        _size = QStandardItem()
        _size.setText("{0:,d}".format(int(size)))
        self._ranges_model.appendRow([_address, _size])

    def on_emulator_start(self):
        pass

    def on_emulator_stop(self):
        self.app.context_panel.set_context(0, 2, self.emulator.current_context)
        # check if the previous hook is waiting for a register result
        if self._require_register_result is not None:
            row = 1
            res = '%s = %s' % (self._require_register_result[1],
                               hex(
                                   self.emulator.uc.reg_read(
                                       self._require_register_result[0])))
            if len(self.assembly._lines) > 1:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row] is None:
                    row = 2
                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res
                # invalidate
                self._require_register_result = None

    def ranges_item_double_clicked(self, model_index):
        row = self._ranges_model.itemFromIndex(model_index).row()
        if row != -1:
            item = self._ranges_model.item(row, 0).text()
            self.memory_table.read_memory(item)
            self.tabs.setCurrentIndex(1)

    def access_item_double_clicked(self, model_index):
        row = self._access_model.itemFromIndex(model_index).row()
        if row != -1:
            item = self._access_model.item(row, 0).text()
            self.memory_table.read_memory(item)
            self.tabs.setCurrentIndex(1)
class NamespaceWidget(QObject):

    error = pyqtSignal(Exception)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        delegate = MyDelegate(self.view, self)
        delegate.error.connect(self.error.emit)
        self.view.setItemDelegate(delegate)
        self.node = None
        self.view.header().setSectionResizeMode(1)
        
        self.addNamespaceAction = QAction("Add Namespace", self.model)
        self.addNamespaceAction.triggered.connect(self.add_namespace)
        self.removeNamespaceAction = QAction("Remove Namespace", self.model)
        self.removeNamespaceAction.triggered.connect(self.remove_namespace)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(self.addNamespaceAction)
        self._contextMenu.addAction(self.removeNamespaceAction)

    @trycatchslot
    def add_namespace(self):
        uries = self.node.get_value()
        newidx = len(uries)
        it = self.model.item(0, 0)
        uri_it = QStandardItem("")
        it.appendRow([QStandardItem(), QStandardItem(str(newidx)), uri_it])
        idx = self.model.indexFromItem(uri_it)
        self.view.edit(idx)

    @trycatchslot
    def remove_namespace(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx == self.model.item(0, 0):
            logger.warning("No valid item selected to remove")
        idx = idx.sibling(idx.row(), 2)
        item = self.model.itemFromIndex(idx)
        uri = item.text()
        uries = self.node.get_value()
        uries.remove(uri)
        logger.info("Writting namespace array: %s", uries)
        self.node.set_value(uries)
        self.reload()

    def set_node(self, node):
        self.model.clear()
        self.node = node
        self.show_array()

    def reload(self):
        self.set_node(self.node)

    def show_array(self):
        self.model.setHorizontalHeaderLabels(['Browse Name', 'Index', 'Value'])

        name_item = QStandardItem(self.node.get_browse_name().Name)
        self.model.appendRow([name_item, QStandardItem(""), QStandardItem()])
        it = self.model.item(0, 0)
        val = self.node.get_value()
        for idx, url in enumerate(val):
            it.appendRow([QStandardItem(), QStandardItem(str(idx)), QStandardItem(url)])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    def showContextMenu(self, position):
        self.removeNamespaceAction.setEnabled(False)
        idx = self.view.currentIndex()
        if not idx.isValid():
            return
        if idx.parent().isValid() and idx.row() >= 1:
            self.removeNamespaceAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
Example #18
0
class LinesDialog(QDialog):
    lines = pyqtSignal(list)
    
    def __element_item(element):
        item = QStandardItem("{} ({})".format(element[2], element[1]) if element[1] else element[2] )
        item.setData({'z': element[0], 'code': element[1], 'name': element[2]})
        return item
    
    def __init__(self, database, settings, plot_widget, axes = None, enable_picker = True, selection_mode = 'multi'):
        super(LinesDialog, self).__init__()
        self.axes = axes if axes else plot_widget.axes
        self.database = database
        self.plot_widget = plot_widget
        self.settings = settings
        self.ui = Ui_LinesDialog()
        self.ui.setupUi(self)
        self.restoreGeometry(self.settings.value('pick_lines_geometry', QByteArray()))
        self.model = QStandardItemModel()
        self.elements_model = QStandardItemModel()
        self.ui.lines.setModel(self.model)
        self.ui.elements.setModel(self.elements_model)
        c = self.database.cursor()
        self.elements_model.appendRow(LinesDialog.__element_item([0, '', 'All']))
        elements = c.execute("SELECT z, code, name FROM elements ORDER BY z ASC")
        for element in elements:
            self.elements_model.appendRow(LinesDialog.__element_item(element))
        
        self.ui.elements.currentTextChanged.connect(lambda t: self.populate())
        self.ui.lambda_from.editingFinished.connect(self.populate)
        self.ui.name.editingFinished.connect(self.populate)
        self.ui.sp_types.toggled.connect(self.populate)
        self.ui.lambda_to.editingFinished.connect(self.populate)
        self.accepted.connect(self.collect_selected_lines)
        self.populate()
        self.ui.pick_wavelengths.setEnabled(enable_picker)
        self.ui.pick_wavelengths.clicked.connect(self.pick_wavelengths_clicked)
        self.ui.lines.setSelectionMode({'multi':QTableView.MultiSelection, 'single':QTableView.SingleSelection}[selection_mode])
        
    def set_picker_enabled(self,enabled):
        self.ui.pick_wavelengths.setEnabled(enabled)
        
    def pick_wavelengths_clicked(self):
        self.plot_widget.add_span_selector("pick_lines_lambda", self.picked_wavelengths, axes=self.axes, direction='horizontal')
        self.lower()
        
    def closeEvent(self, ev):
        self.settings.setValue('pick_lines_geometry', self.saveGeometry())
        QDialog.closeEvent(self, ev)
        
    def picked_wavelengths(self, start, end):
        self.raise_()
        self.ui.lambda_from.setValue(start)
        self.ui.lambda_to.setValue(end)
        self.populate()

    def collect_selected_lines(self):
        selected_rows = self.ui.lines.selectionModel().selectedRows()
        if selected_rows:
            self.lines.emit([self.model.itemFromIndex(i).data() for i in selected_rows])
        
    def populate(self):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['Lambda', 'Element', 'Atomic number', 'Ionization', 'Stellar spectral types'])
        c = self.database.cursor()
        query = "SELECT lambda, Element, Z, Ion, SpTypes from spectral_lines WHERE {} ORDER BY lambda ASC;"
        conditions = ['(1 = 1)']
        
        element = self.elements_model.item(self.ui.elements.currentIndex()).data()
        if element['z']:
            conditions.append("(Z = {})".format(element['z']))
        
        if self.ui.lambda_from.value() > 0:
            conditions.append("(Lambda >= {})".format(self.ui.lambda_from.value()))
        if self.ui.lambda_to.value() > 0:
            conditions.append("(Lambda <= {})".format(self.ui.lambda_to.value()))
        if self.ui.name.text():
            conditions.append("(Element like '%{}%')".format(self.ui.name.text()))
        if self.ui.sp_types.isChecked():
            conditions.append("(SpTypes <> '')")
        
        for row in c.execute(query.format(" AND ".join(conditions))):
            first_item = QStandardItem("{}".format(row[0]))
            first_item.setData({'lambda': row[0], 'name': row[1], 'z': row[2]})
            self.model.appendRow( [
                first_item,
                QStandardItem(row[1]),
                QStandardItem("{}".format(row[2])),
                QStandardItem("{}".format(row[3])),
                QStandardItem(row[4])
                ])
            
    def keyPressEvent(self, evt):
      if evt.key() == Qt.Key_Enter or evt.key() == Qt.Key_Return:
        return
        QDialog.keyPressEvent(self.evt)
Example #19
0
class RefsWidget(QObject):

    error = pyqtSignal(Exception)
    reference_changed = pyqtSignal(Node)

    def __init__(self, view):
        self.view = view
        QObject.__init__(self, view)
        self.model = QStandardItemModel()

        delegate = MyDelegate(self.view, self)
        delegate.error.connect(self.error.emit)
        delegate.reference_changed.connect(self.reference_changed.emit)
        self.view.setEditTriggers(QAbstractItemView.DoubleClicked)
        self.view.setModel(self.model)
        self.view.setItemDelegate(delegate)
        self.settings = QSettings()
        self.model.setHorizontalHeaderLabels(['ReferenceType', 'NodeId', "BrowseName", "TypeDefinition"])
        state = self.settings.value("WindowState/refs_widget_state", None)
        if state is not None:
            self.view.horizontalHeader().restoreState(state)
        self.view.horizontalHeader().setSectionResizeMode(0)
        self.view.horizontalHeader().setStretchLastSection(True)
        self.node = None

        self.reloadAction = QAction("Reload", self.model)
        self.reloadAction.triggered.connect(self.reload)
        self.addRefAction = QAction("Add Reference", self.model)
        self.addRefAction.triggered.connect(self.add_ref)
        self.removeRefAction = QAction("Remove Reference", self.model)
        self.removeRefAction.triggered.connect(self.remove_ref)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(self.reloadAction)
        self._contextMenu.addSeparator()
        self._contextMenu.addAction(self.addRefAction)
        self._contextMenu.addAction(self.removeRefAction)

    def showContextMenu(self, position):
        if not self.node:
            return
        self.removeRefAction.setEnabled(False)
        idx = self.view.currentIndex()
        if idx.isValid():
            self.removeRefAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))

    def clear(self):
        # remove all rows but not header!!
        self.model.removeRows(0, self.model.rowCount())
        self.node = None

    def _make_default_ref(self):
        #FIXME: remeber last choosen values or use values that make sense
        ref = ua.ReferenceDescription()
        return ref

    @trycatchslot
    def add_ref(self):
        ref = self._make_default_ref()
        logger.info("Adding ref: %s", ref)
        self._add_ref_row(ref)
        idx = self.model.index(self.model.rowCount() - 1, 0)
        self.view.setCurrentIndex(idx)
        #self.view.edit(idx)

    @trycatchslot
    def reload(self):
        node = self.node
        self.clear()
        self.show_refs(node)

    @trycatchslot
    def remove_ref(self):
        idx = self.view.currentIndex()
        if not idx.isValid():
            logger.warning("No valid reference selected to remove")
        idx = idx.sibling(idx.row(), 0)
        item = self.model.itemFromIndex(idx)
        ref = item.data(Qt.UserRole)
        self.do_remove_ref(ref)
        self.reload()
    
    def do_remove_ref(self, ref, check=True):
        logger.info("Removing: %s", ref)
        it = ua.DeleteReferencesItem()
        it.SourceNodeId = self.node.nodeid
        it.ReferenceTypeId = ref.ReferenceTypeId
        it.IsForward = ref.IsForward
        it.TargetNodeId = ref.NodeId
        it.DeleteBidirectional = False
        #param = ua.DeleteReferencesParameters()
        #param.ReferencesToDelete.append(it)
        results = self.node.server.delete_references([it])
        logger.info("Remove result: %s", results[0]) 
        if check:
            results[0].check()

    def save_state(self):
        self.settings.setValue("WindowState/refs_widget_state", self.view.horizontalHeader().saveState())

    def show_refs(self, node):
        self.clear()
        self.node = node
        self._show_refs(node)

    def _show_refs(self, node):
        try:
            refs = node.get_children_descriptions(refs=ua.ObjectIds.References)
        except Exception as ex:
            self.error.emit(ex)
            raise
        for ref in refs:
            self._add_ref_row(ref)

    def _add_ref_row(self, ref):
        if ref.ReferenceTypeId.Identifier in ua.ObjectIdNames:
            typename = ua.ObjectIdNames[ref.ReferenceTypeId.Identifier]
        else:
            typename = str(ref.ReferenceTypeId)
        if ref.NodeId.NamespaceIndex == 0 and ref.NodeId.Identifier in ua.ObjectIdNames:
            nodeid = ua.ObjectIdNames[ref.NodeId.Identifier]
        else:
            nodeid = ref.NodeId.to_string()
        if ref.TypeDefinition.Identifier in ua.ObjectIdNames:
            typedef = ua.ObjectIdNames[ref.TypeDefinition.Identifier]
        else:
            typedef = ref.TypeDefinition.to_string()
        titem = QStandardItem(typename)
        titem.setData(ref, Qt.UserRole)
        self.model.appendRow([
            titem,
            QStandardItem(nodeid),
            QStandardItem(ref.BrowseName.to_string()),
            QStandardItem(typedef)
        ])
class RefNodeSetsWidget(QObject):

    error = pyqtSignal(Exception)
    nodeset_added = pyqtSignal(str)
    nodeset_removed = pyqtSignal(str)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.nodesets = []
        self.server_mgr = None
        self.view.header().setSectionResizeMode(1)
        
        addNodeSetAction = QAction("Add Reference Node Set", self.model)
        addNodeSetAction.triggered.connect(self.add_nodeset)
        self.removeNodeSetAction = QAction("Remove Reference Node Set", self.model)
        self.removeNodeSetAction.triggered.connect(self.remove_nodeset)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(addNodeSetAction)
        self._contextMenu.addAction(self.removeNodeSetAction)

    @trycatchslot
    def add_nodeset(self):
        path, ok = QFileDialog.getOpenFileName(self.view, caption="Import OPC UA XML Node Set", filter="XML Files (*.xml *.XML)", directory=".")
        if not ok:
            return None
        self.import_nodeset(path)

    def import_nodeset(self, path):
        print("IMPORT", path)
        name = os.path.basename(path)
        if name in self.nodesets:
            return
        try:
            self.server_mgr.import_xml(path)
        except Exception as ex:
            self.error.emit(ex)
            raise

        item = QStandardItem(name)
        self.model.appendRow([item])
        self.nodesets.append(name)
        self.view.expandAll()
        self.nodeset_added.emit(path)

    @trycatchslot
    def remove_nodeset(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            return

        item = self.model.itemFromIndex(idx)
        name = item.text()
        self.nodesets.remove(name)
        self.model.removeRow(idx.row())
        self.nodeset_removed.emit(name)

    def set_server_mgr(self, server_mgr):
        self.server_mgr = server_mgr
        self.nodesets = []
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['Node Sets'])
        item = QStandardItem("Opc.Ua.NodeSet2.xml")
        item.setFlags(Qt.NoItemFlags)
        self.model.appendRow([item])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    @trycatchslot
    def showContextMenu(self, position):
        if not self.server_mgr:
            return
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            self.removeNodeSetAction.setEnabled(False)
        else:
            self.removeNodeSetAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
Example #21
0
class QWList(QListView):
    """Widget for List
    """
    def __init__(self, **kwargs):

        QListView.__init__(self, kwargs.get('parent', None))
        #self._name = self.__class__.__name__

        #icon.set_icons()

        self.model = QStandardItemModel()
        self.set_selection_mode()
        self.fill_list_model(**kwargs)  # defines self.model
        self.setModel(self.model)

        self.set_style()
        self.show_tool_tips()

        self.connect_signals()
        #self.disconnect_signals()

    def connect_signals(self):
        self.model.itemChanged.connect(self.on_item_changed)
        self.connect_item_selected_to(self.on_item_selected)
        self.clicked[QModelIndex].connect(self.on_click)
        self.doubleClicked[QModelIndex].connect(self.on_double_click)

    def disconnect_signals(self):
        self.model.itemChanged.disconnect(self.on_item_changed)
        self.disconnect_item_selected_from(self.on_item_selected)
        self.clicked[QModelIndex].disconnect(self.on_click)
        self.doubleClicked[QModelIndex].disconnect(self.on_double_click)

    def set_selection_mode(self, smode='extended'):
        logger.debug('Set selection mode: %s' % smode)
        mode = {
            'single': QAbstractItemView.SingleSelection,
            'contiguous': QAbstractItemView.ContiguousSelection,
            'extended': QAbstractItemView.ExtendedSelection,
            'multi': QAbstractItemView.MultiSelection,
            'no selection': QAbstractItemView.NoSelection
        }[smode]
        self.setSelectionMode(mode)

    def connect_item_selected_to(self, recipient):
        self.selectionModel().currentChanged[QModelIndex,
                                             QModelIndex].connect(recipient)

    def disconnect_item_selected_from(self, recipient):
        self.selectionModel().currentChanged[QModelIndex,
                                             QModelIndex].disconnect(recipient)

    def selected_indexes(self):
        return self.selectedIndexes()

    def selected_items(self):
        indexes = self.selectedIndexes()
        return [self.model.itemFromIndex(i) for i in self.selectedIndexes()]

    def clear_model(self):
        rows = self.model.rowCount()
        self.model.removeRows(0, rows)

    def fill_list_model(self, **kwargs):
        self.clear_model()
        for i in range(20):
            item = QStandardItem('%02d item text' % (i))
            #item.setIcon(icon.icon_table)
            item.setCheckable(True)
            self.model.appendRow(item)

    def on_item_selected(self, selected, deselected):
        itemsel = self.model.itemFromIndex(selected)
        if itemsel is not None:
            msg = 'on_item_selected row:%02d selected: %s' % (selected.row(),
                                                              itemsel.text())
            logger.info(msg)

        #itemdes = self.model.itemFromIndex(deselected)
        #if itemdes is not None :
        #    msg = 'on_item_selected row: %d deselected %s' % (deselected.row(), itemdes.text())
        #    logger.info(msg)

    def on_item_changed(self, item):
        state = ['UNCHECKED', 'TRISTATE', 'CHECKED'][item.checkState()]
        msg = 'on_item_changed: item "%s", is at state %s' % (item.text(),
                                                              state)
        logger.info(msg)

    def on_click(self, index):
        item = self.model.itemFromIndex(index)
        txt = item.text()
        txtshow = txt if len(txt) < 50 else '%s...' % txt[:50]
        msg = 'doc clicked in row%02d: %s' % (index.row(), txtshow)
        logger.info(msg)

    def on_double_click(self, index):
        item = self.model.itemFromIndex(index)
        msg = 'on_double_click item in row:%02d text: %s' % (index.row(),
                                                             item.text())
        logger.debug(msg)

    #--------------------------
    #--------------------------
    #--------------------------

    def show_tool_tips(self):
        self.setToolTip('List model')

    def set_style(self):
        #from psana.graphqt.Styles import style
        #self.setWindowIcon(icon.icon_monitor)
        #self.layout().setContentsMargins(0,0,0,0)

        self.setStyleSheet("QListView::item:hover{background-color:#00FFAA;}")

        #self.palette = QPalette()
        #self.resetColorIsSet = False
        #self.butELog    .setIcon(icon.icon_mail_forward)
        #self.butFile    .setIcon(icon.icon_save)
        self.setMinimumHeight(100)
        self.setMinimumWidth(50)
        #self.adjustSize()
        #self.        setStyleSheet(style.styleBkgd)
        #self.butSave.setStyleSheet(style.styleButton)
        #self.butFBrowser.setVisible(False)
        #self.butExit.setText('')
        #self.butExit.setFlat(True)

    #def resizeEvent(self, e):
    #pass
    #self.frame.setGeometry(self.rect())
    #logger.debug('resizeEvent')

    #def moveEvent(self, e):
    #logger.debug('moveEvent')
    #self.position = self.mapToGlobal(self.pos())
    #self.position = self.pos()
    #logger.debug('moveEvent - pos:' + str(self.position), __name__)
    #pass

    def closeEvent(self, e):
        logger.debug('closeEvent')
        QListView.closeEvent(self, e)

        #try    : self.gui_win.close()
        #except : pass

        #try    : del self.gui_win
        #except : pass

    def on_exit(self):
        logger.debug('on_exit')
        self.close()

    def process_selected_items(self):
        selitems = self.selected_items()
        msg = '%d Selected items:' % len(selitems)
        for i in selitems:
            msg += '\n  %s' % i.text()
        logger.info(msg)

    if __name__ == "__main__":

        def key_usage(self):
            return 'Keys:'\
                   '\n  ESC - exit'\
                   '\n  S - show selected items'\
                   '\n'

        def keyPressEvent(self, e):
            #logger.info('keyPressEvent, key=', e.key())
            if e.key() == Qt.Key_Escape:
                self.close()

            elif e.key() == Qt.Key_S:
                self.process_selected_items()

            else:
                logger.info(self.key_usage())
Example #22
0
class MyMainWindow(Ui_MainWindow):
    """
    main window class
    """
    def __init__(self, application):
        super().__init__()
        self.application = application
        self.homeFolder = expanduser("~")
        self.properties_over_all_layers_per_tab = {}
        self.last_loaded_bitmap = ""

    def finishSetupUi(self):
        """
        initialize models, tabs
        :return: nothing
        """
        self.scene = QGraphicsScene()
        self.graphicsView.setScene(self.scene)
        self.bitmap = None
        self.bitmapItem = None
        self.previousPath = None
        self.homeFolder = None
        self.layersModel = QStandardItemModel()
        self.layersList.setModel(self.layersModel)
        self.previousActiveLayer = None
        all_tabs = TABS_WITH_PER_LAYER_PARAMS.copy()
        all_tabs.extend(TABS_OVER_ALL_LAYERS)
        self.tabs = all_tabs
        self.tabhandlers = {
            t.get_id(): t(self, self.layersModel)
            for t in self.tabs
        }
        self.AddLayer()
        self.layersList.setCurrentIndex(self.layersModel.index(0, 0))
        self.bitmapVisibility = True
        self.generated = {}

    def setupSlots(self):
        """
        called to set up mainwindow and tab slots
        :return:
        """
        self.loadBitmap.clicked.connect(self.LoadBitmap)
        self.hideBitmap.clicked.connect(self.ToggleBitmap)
        self.actionZoom_In.triggered.connect(self.ActionZoom_In)
        self.actionZoom_to.triggered.connect(self.ActionZoom_to)
        self.actionZoom_Out.triggered.connect(self.ActionZoom_Out)

        self.actionLoad_Bitmap.triggered.connect(self.LoadBitmap)
        self.actionExport_SVG.triggered.connect(self.ExportSVG)
        self.actionShow_toolbar.triggered.connect(self.ShowToolbar)
        self.actionQuit.triggered.connect(self.OnQuit)
        self.actionQuit_2.triggered.connect(self.OnQuit)
        self.actionShow_layers.triggered.connect(self.ShowLayers)
        self.actionExport_SVG_one_file_per_layer.triggered.connect(
            self.ExportSVGPerLayer)

        self.application.aboutToQuit.connect(self.OnQuit)

        self.addLayer.clicked.connect(self.AddLayer)
        self.removeLayer.clicked.connect(self.RemoveSelected)
        self.layersModel.itemChanged.connect(self.LayerChanged)
        self.layersList.clicked.connect(self.LayerSelectionChanged)
        self.exportSvg.clicked.connect(self.ExportSVG)
        self.exportSvgPerLayer.clicked.connect(self.ExportSVGPerLayer)
        for t in self.tabhandlers:
            self.tabhandlers[t].setupSlots()
            self.tabhandlers[t].last_used_method.connect(
                self.UpdateLastUsedMethod)

        self.saveSketch.clicked.connect(self.SaveSketch)
        self.loadSketch.clicked.connect(self.LoadSketch)

    def LoadSketch(self):
        fname = QFileDialog.getOpenFileName(self.centralwidget, 'Open sketch',
                                            self.homeFolder,
                                            "Sketch files (*.sq)")[0]
        if not fname:
            return

        with open(fname, 'r') as stream:
            try:
                summary_model = yaml.safe_load(stream)
            except yaml.YAMLError as exc:
                QMessageBox.critical(
                    self.graphicsView, "Error",
                    "Problem loading .sq file!! Reason: {0}".format(exc))
                return

        for layer_idx in range(self.layersModel.rowCount()):
            gfx = self.layersModel.item(layer_idx).get_graphics_items_group()
            if gfx is not None:
                self.scene.removeItem(gfx)
        self.layersModel.clear()

        self.properties_over_all_layers_per_tab = {}
        self.last_loaded_bitmap = ""

        if summary_model["bitmap"]:
            self.load_bitmap_file(summary_model["bitmap"])

        if not summary_model["bitmap_visible"]:
            self.ToggleBitmap()

        no_of_layers = len(summary_model["layer_dependent_parameters"]
                           ["method_parameters"].keys())
        for l in range(no_of_layers):
            self.AddLayer()
            for tab in TABS_WITH_PER_LAYER_PARAMS:
                tabidx = tab.get_id()
                self.layersModel.item(l, 0).set_parameters_for_tab(
                    tabidx, summary_model["layer_dependent_parameters"]
                    ["method_parameters"][l][tabidx])
                self.layersModel.item(l, 0).set_last_used_method(
                    summary_model["layer_dependent_parameters"]
                    ["last_used_method"][l])
                self.tabhandlers[tabidx].model_to_ui(
                    summary_model["layer_dependent_parameters"]
                    ["method_parameters"][l][tabidx])

        self.properties_over_all_layers_per_tab = summary_model[
            "layer_independent_parameters"]
        for tab in TABS_OVER_ALL_LAYERS:
            tabidx = tab.get_id()
            self.tabhandlers[tabidx].model_to_ui(
                self.properties_over_all_layers_per_tab[tabidx])

        if self.last_loaded_bitmap:
            for l in range(no_of_layers):
                self.layersList.setCurrentIndex(self.layersModel.index(l, 0))
                for tab in TABS_WITH_PER_LAYER_PARAMS:
                    tabidx = tab.get_id()
                    if tabidx == summary_model["layer_dependent_parameters"][
                            "last_used_method"][l]:
                        self.tabhandlers[tabidx].process_without_signals()

        for l in range(no_of_layers):
            self.layersModel.item(l, 0).setCheckState(
                Qt.Checked if summary_model["layer_dependent_parameters"]
                ["layer_enabled"][l] else Qt.Unchecked)

    def SaveSketch(self):
        fname = QFileDialog.getSaveFileName(self.centralwidget, 'Save sketch',
                                            self.homeFolder,
                                            "Sketch files (*.sq)")[0]
        if not fname:
            return

        # make sure latest modifications are saved as well!
        item = self.layersModel.itemFromIndex(self.layersList.currentIndex())
        self.update_layer_item_model_from_ui(item)

        # build a model to save
        summary_model = {}
        summary_model["version"] = "0.0.1"
        summary_model["bitmap"] = self.last_loaded_bitmap
        summary_model["bitmap_visible"] = self.bitmapVisibility
        summary_model[
            "layer_independent_parameters"] = self.properties_over_all_layers_per_tab
        summary_model["layer_dependent_parameters"] = {}
        for l in range(self.layersModel.rowCount()):
            data = self.layersModel.item(l, 0).get_parameters()
            if 'method_parameters' not in summary_model[
                    "layer_dependent_parameters"]:
                summary_model["layer_dependent_parameters"][
                    "method_parameters"] = {}
            summary_model["layer_dependent_parameters"]["method_parameters"][
                l] = data
            last_used_method = self.layersModel.item(l,
                                                     0).get_last_used_method()
            if 'last_used_method' not in summary_model[
                    "layer_dependent_parameters"]:
                summary_model["layer_dependent_parameters"][
                    "last_used_method"] = {}
            summary_model["layer_dependent_parameters"]["last_used_method"][
                l] = last_used_method
            if 'layer_enabled' not in summary_model[
                    "layer_dependent_parameters"]:
                summary_model["layer_dependent_parameters"][
                    "layer_enabled"] = {}
            summary_model["layer_dependent_parameters"]["layer_enabled"][
                l] = self.layersModel.item(l, 0).checkState() == Qt.Checked
        with open(fname, 'w') as outfile:
            yaml.dump(summary_model, outfile, default_flow_style=False)

    def UpdateLastUsedMethod(self, layer_model_index, method):
        self.layersModel.itemFromIndex(layer_model_index).set_last_used_method(
            method)

    def ShowToolbar(self):
        """
        action triggered when user requests to see buttons toolbar
        :return:
        """
        self.toolBar.setVisible(True)

    def ShowLayers(self):
        """
        action triggered when user requests to see layers toolbar
        :return:
        """
        self.layers.setVisible(True)

    def OnQuit(self):
        """
        action triggered when the application is about to quit -> give each tab a chance to clean up after itself
        :return:
        """
        for t in self.tabhandlers:
            self.tabhandlers[t].on_quit()
        import sys
        sys.exit()

    def LoadBitmap(self):
        """
        action triggered when the user requests to load a bitmap
        :return:
        """
        fname = QFileDialog.getOpenFileName(
            self.centralwidget, 'Open file', self.homeFolder,
            "Image files (*.jpg *.jpeg *.gif *.png *.bmp)")[0]
        if not fname:
            return

        self.load_bitmap_file(fname)

    def load_bitmap_file(self, fname):
        # clean up all old data
        for layer_idx in range(self.layersModel.rowCount()):
            gfx = self.layersModel.item(layer_idx).get_graphics_items_group()
            if gfx:
                self.scene.removeItem(gfx)
        # create new data
        self.bitmap = QImage(fname)
        if self.bitmap:
            if self.bitmapItem is not None:
                self.scene.removeItem(self.bitmapItem)
            self.bitmapItem = QGraphicsPixmapItem(
                QPixmap.fromImage(self.bitmap))
            self.scene.addItem(self.bitmapItem)
            self.hideBitmap.setText("Hide Bitmap")
            self.bitmapVisibility = True
        for t in self.tabhandlers:
            self.tabhandlers[t].after_load_bitmap()
        self.last_loaded_bitmap = fname

    def ActionZoom_In(self):
        """
        action triggered when user requests zooming in using mousewheel
        :return:
        """
        # Zoom Factor
        zoomInFactor = 1.25
        self.graphicsView.scale(zoomInFactor, zoomInFactor)

    def ActionZoom_Out(self):
        """
        action triggered when user requests zooming out using mousewheel
        :return:
        """
        # Zoom Factor
        zoomInFactor = 1 / 1.25
        self.graphicsView.scale(zoomInFactor, zoomInFactor)

    def ActionZoom_to(self):
        """
        action triggered when user requests zoom to fit
        :return:
        """
        self.graphicsView.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio)

    def AddLayer(self):
        """
        action triggered when user requests addition of a new layer
        :return:
        """
        item = LayerItem("Layer {0}".format(self.layersModel.rowCount() + 1))
        self.update_layer_item_model_from_ui(item)
        self.layersModel.appendRow(item)

    def update_layer_item_model_from_ui(self, item):
        for tab in TABS_WITH_PER_LAYER_PARAMS:
            tabidx = tab.get_id()
            item.set_parameters_for_tab(tabidx,
                                        self.tabhandlers[tabidx].ui_to_model())
        for tab in TABS_OVER_ALL_LAYERS:
            tabidx = tab.get_id()
            self.properties_over_all_layers_per_tab[tabidx] = self.tabhandlers[
                tabidx].ui_to_model()

    def RemoveSelected(self):
        """
        action triggered when user requests removal of selected layer
        :return:
        """
        for i in self.layersList.selectedIndexes():
            gfx = self.layersModel.itemFromIndex(i).get_graphics_items_group()
            if gfx:
                self.scene.removeItem(gfx)
            self.layersModel.itemFromIndex(i).remove_graphics_items_group()
            self.layersModel.removeRow(i.row())
        if self.layersModel.rowCount() == 0:
            self.AddLayer()
            self.layersList.setCurrentIndex(self.layersModel.index(0, 0))

    def ToggleBitmap(self):
        """
        action triggered when user requests to hide/show bitmap
        :return:
        """
        if self.bitmapVisibility:
            if self.bitmapItem:
                self.scene.removeItem(self.bitmapItem)
            self.hideBitmap.setText("Show Bitmap")
            self.bitmapVisibility = False
        else:
            if self.bitmapItem:
                self.bitmapItem.setZValue(-1)
                self.scene.addItem(self.bitmapItem)
            self.hideBitmap.setText("Hide Bitmap")
            self.bitmapVisibility = True

    def LayerSelectionChanged(self, new_layer):
        persistent_new_layer = QPersistentModelIndex(new_layer)
        if self.previousActiveLayer is None:
            previous_layer = QPersistentModelIndex(self.layersModel.index(
                0, 0))
        else:
            previous_layer = self.previousActiveLayer
        self.previousActiveLayer = persistent_new_layer

        # remember parameter settings from previous layer when switching to new layer
        # store layer dependent parameters
        if previous_layer.isValid():  # could have been deleted...
            for t in TABS_WITH_PER_LAYER_PARAMS:
                tabidx = t.get_id()
                self.layersModel.itemFromIndex(QModelIndex(previous_layer)).parameters_per_tab[tabidx] = \
                    self.tabhandlers[tabidx].ui_to_model()

        # store overall parameters
        for t in TABS_OVER_ALL_LAYERS:
            tabidx = t.get_id()
            self.properties_over_all_layers_per_tab[tabidx] = self.tabhandlers[
                tabidx].ui_to_model()

        # show parameter settings on newly selected layer if they were stored in a previous visit already
        for t in TABS_WITH_PER_LAYER_PARAMS:
            tabidx = t.get_id()
            self.tabhandlers[tabidx].model_to_ui(
                self.layersModel.itemFromIndex(
                    new_layer).get_parameters_for_tab(tabidx))

        # restore overall parameters
        for t in TABS_OVER_ALL_LAYERS:
            tabidx = t.get_id()
            if tabidx in self.properties_over_all_layers_per_tab:
                self.tabhandlers[tabidx].model_to_ui(
                    self.properties_over_all_layers_per_tab[tabidx])

        # also make last used method the active tab
        last_used_method = self.layersModel.itemFromIndex(
            new_layer).get_last_used_method()
        self.squigglifySetup.setCurrentIndex(TAB_ORDER[last_used_method])

    def LayerChanged(self):
        """
        action triggered when user checks/unchecks a layer
        :return:
        """
        numRows = self.layersModel.rowCount()
        for row in range(numRows):
            item = self.layersModel.item(row)
            if item.checkState() == Qt.Checked:
                g = item.get_graphics_items_group()
                if g:
                    g.setVisible(True)
            else:
                g = item.get_graphics_items_group()
                if g:
                    g.setVisible(False)

    def ExportSVG(self):
        """
        action triggered when user exports current graphics as svg
        :return:
        """
        newPath = QFileDialog.getSaveFileName(self.centralwidget,
                                              "Export .svg", self.homeFolder,
                                              "SVG files (*.svg)")
        if not newPath[0]:
            return

        filename = newPath[0]
        if self.bitmapVisibility:
            self.ToggleBitmap()
        generator = QSvgGenerator()
        generator.setFileName(filename)
        sceneSize = self.scene.sceneRect().size()
        generator.setSize(sceneSize.toSize())
        generator.setViewBox(QRect(0, 0, sceneSize.width(),
                                   sceneSize.height()))
        generator.setDescription("generated by SVG generator")
        generator.setTitle(filename)
        painter = QPainter()
        painter.begin(generator)
        self.scene.render(painter)
        painter.end()

    def ExportSVGPerLayer(self):
        """
        action triggered when user exports current graphics as one svg per layer
        :return:
        """
        newPath = QFileDialog.getSaveFileName(self.centralwidget,
                                              "Export .svg per layer",
                                              self.homeFolder,
                                              "SVG files (*.svg)")
        if not newPath[0]:
            return

        filename = newPath[0]

        if self.bitmapVisibility:
            self.ToggleBitmap()

        # make stuff invisible to avoid sending it to svg
        for layer_idx in range(self.layersModel.rowCount()):
            print("*** iteration {0}".format(layer_idx))
            layer_filename = "{0}_layer{1}.svg".format(
                os.path.splitext(filename)[0], layer_idx + 1)
            for item in self.scene.items():
                item.setVisible(True)
                forceAlwaysInvisible = item.__class__ == QGraphicsPixmapItem
                forceInvisibleInCurrentLayer = item.__class__ == QGraphicsItemGroup and \
                                               (item != self.layersModel.item(layer_idx).get_graphics_items_group() or \
                                                self.layersModel.item(layer_idx).checkState() != Qt.Checked)
                if forceAlwaysInvisible or forceInvisibleInCurrentLayer:  # ouch
                    print("setting idx to invisible for item {0}".format(item))
                    item.setVisible(False)
            if self.layersModel.item(layer_idx).checkState() == Qt.Checked:
                generator = QSvgGenerator()
                generator.setFileName(layer_filename)
                sceneSize = self.scene.sceneRect().size()
                generator.setSize(sceneSize.toSize())
                generator.setViewBox(
                    QRect(0, 0, sceneSize.width(), sceneSize.height()))
                generator.setDescription("generated by SVG generator")
                generator.setTitle(layer_filename)
                painter = QPainter()
                painter.begin(generator)
                self.scene.render(painter)
                painter.end()

        # restore visibility
        for layer_idx in range(self.layersModel.rowCount()):
            for item in self.scene.items():
                if item.__class__ == QGraphicsItemGroup and \
                        self.layersModel.item(layer_idx).checkState() == Qt.Checked:  # ouch
                    item.setVisible(True)

    def get_sketch_code(self):
        """
        method used to be able to call code generation (implemented in gcodetab) from gcodesendertab
        :return: an object containing gcode
        """
        return self.tabhandlers[GcodeTab.get_id()].get_sketch_code()

    def get_sketch_by_layer(self):
        """
        method used to be able to call code generation (implemented in gcodetab) from gcodesendertab
        :return: a list of objects containing gcode
        """
        return self.tabhandlers[GcodeTab.get_id()].get_sketch_by_layer()

    def check_drawing_fits(self):
        """
        method used to check that drawing fits within margins (implemented in gcodetab) from gcodesendertab
        :return: boolean
        """
        return self.tabhandlers[GcodeTab.get_id()].check_drawing_fits()
Example #23
0
class SearchWidget(QWidget):

    startWork = pyqtSignal(str)
    resultSelected = pyqtSignal(str)

    def __init__(self, parentWidget):
        QWidget.__init__(self, parentWidget)
        
        self.editorWidget = parentWidget.editorWidget   # TODO: Review class structure

        self.searching = False
        self.ui = Ui_SearchWidget()
        self.ui.setupUi(self)
        self.resultListModel = QStandardItemModel(self.ui.resultList)
        self.ui.resultWidget.setCurrentIndex(0)
        self.ui.resultList.setModel(self.resultListModel)
        self.ui.resultList.selectionModel().selectionChanged.connect(self.doResultSelected)

        self.startIcon = QIcon(':/icons/search-global-start.png')
        self.stopIcon = QIcon(':/icons/search-global-stop.png')
        self.ui.startStopButton.setIcon(self.startIcon)

        self.ui.searchInput.returnPressed.connect(self.doReturnKey)
        self.ui.startStopButton.clicked.connect(self.doStartStopButton)

        self.workerThread = QThread()
        self.worker = SearchWorker()
        self.worker.moveToThread(self.workerThread)
        self.startWork.connect(self.worker.startSearch)
        self.worker.searchDone.connect(self.searchDone)
        self.worker.addMatch.connect(self.addMatch)
        # self.stopWork.connect(self.worker.stopSearch)
        self.workerThread.start()


    def doReturnKey(self):
        if self.searching:
            self.worker.stopSearch()        # must not call trough a slot since it would be queued
                                            # ATTENTION! Still a race condition!!! (At least when the search process just finished normally)
        self.startSearch()


    def doStartStopButton(self):
        if self.searching:
            self.worker.stopSearch()        # must not call trough a slot since it would be queued
            self.searchDone()
        else: 
            self.startSearch()


    def startSearch(self):
        self.searching = True
        self.ui.startStopButton.setIcon(self.stopIcon)
        self.resultListModel.clear()
        queryText = self.ui.searchInput.text()
        # rootPath = self.editorWidget.page.notepad.getRootpath()

        # Setup worker with required data
        self.worker.notepad = self.editorWidget.page.notepad
        # NOTE: we are querying the page list in this thread,
        # to avoid concurrency issues with SQLite.
        self.worker.pageList = self.editorWidget.page.notepad.getAllPages()
        self.startWork.emit(queryText)


    def searchDone(self):
        print("Search Done.")
        self.searching = False
        if self.resultListModel.rowCount() == 0:
            self.ui.resultWidget.setCurrentIndex(1)  # show error page
        self.ui.startStopButton.setIcon(self.startIcon)

    def addMatch(self, pageId):
        # print("    Adding: {}".format(pageId))
        self.ui.resultWidget.setCurrentIndex(0)     # make sure to show the list
        resultItem = QStandardItem(pageId)
        resultItem.setEditable(False)
        self.resultListModel.appendRow(resultItem)


    def doResultSelected(self, selectedtItem, idx2):
        indexes = selectedtItem.indexes()
        if len(indexes) == 1:
            item = self.resultListModel.itemFromIndex(indexes[0])
            pageId = item.text()
            self.resultSelected.emit(pageId)
Example #24
0
class HooksWidget(QWidget):
    """ HooksPanel

        Signals:
            onShowMemoryRequest(str) - ptr
            onHookChanged(str) - ptr
            onHookRemoved(str) - ptr
    """

    onHookChanged = pyqtSignal(str, name='onHookChanged')
    onHookRemoved = pyqtSignal(str, name='onHookRemoved')

    def __init__(self, parent=None):  # pylint: disable=too-many-statements
        super(HooksWidget, self).__init__(parent=parent)

        self._app_window = parent

        if self._app_window.dwarf is None:
            print('HooksPanel created before Dwarf exists')
            return

        # connect to dwarf
        self._app_window.dwarf.onAddJavaHook.connect(self._on_add_hook)
        self._app_window.dwarf.onAddNativeHook.connect(self._on_add_hook)
        self._app_window.dwarf.onAddNativeOnLoadHook.connect(self._on_add_hook)
        self._app_window.dwarf.onAddJavaOnLoadHook.connect(self._on_add_hook)
        self._app_window.dwarf.onHitNativeOnLoad.connect(
            self._on_hit_native_on_load)
        self._app_window.dwarf.onHitJavaOnLoad.connect(
            self._on_hit_java_on_load)
        self._app_window.dwarf.onDeleteHook.connect(self._on_hook_deleted)

        self._hooks_list = DwarfListView()
        self._hooks_list.doubleClicked.connect(self._on_double_clicked)
        self._hooks_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self._hooks_list.customContextMenuRequested.connect(
            self._on_context_menu)
        self._hooks_model = QStandardItemModel(0, 5)

        self._hooks_model.setHeaderData(0, Qt.Horizontal, 'Address')
        self._hooks_model.setHeaderData(1, Qt.Horizontal, 'T')
        self._hooks_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter,
                                        Qt.TextAlignmentRole)
        self._hooks_model.setHeaderData(2, Qt.Horizontal, 'Input')
        self._hooks_model.setHeaderData(3, Qt.Horizontal, '{}')
        self._hooks_model.setHeaderData(3, Qt.Horizontal, Qt.AlignCenter,
                                        Qt.TextAlignmentRole)
        self._hooks_model.setHeaderData(4, Qt.Horizontal, '<>')
        self._hooks_model.setHeaderData(4, Qt.Horizontal, Qt.AlignCenter,
                                        Qt.TextAlignmentRole)

        self._hooks_list.setModel(self._hooks_model)

        self._hooks_list.header().setStretchLastSection(False)
        self._hooks_list.header().setSectionResizeMode(
            0, QHeaderView.ResizeToContents | QHeaderView.Interactive)
        self._hooks_list.header().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        self._hooks_list.header().setSectionResizeMode(2, QHeaderView.Stretch)
        self._hooks_list.header().setSectionResizeMode(
            3, QHeaderView.ResizeToContents)
        self._hooks_list.header().setSectionResizeMode(
            4, QHeaderView.ResizeToContents)

        v_box = QVBoxLayout(self)
        v_box.setContentsMargins(0, 0, 0, 0)
        v_box.addWidget(self._hooks_list)
        #header = QHeaderView(Qt.Horizontal, self)

        h_box = QHBoxLayout()
        h_box.setContentsMargins(5, 2, 5, 5)
        self.btn1 = QPushButton(
            QIcon(utils.resource_path('assets/icons/plus.svg')), '')
        self.btn1.setFixedSize(20, 20)
        self.btn1.clicked.connect(self._on_additem_clicked)
        btn2 = QPushButton(QIcon(utils.resource_path('assets/icons/dash.svg')),
                           '')
        btn2.setFixedSize(20, 20)
        btn2.clicked.connect(self.delete_items)
        btn3 = QPushButton(
            QIcon(utils.resource_path('assets/icons/trashcan.svg')), '')
        btn3.setFixedSize(20, 20)
        btn3.clicked.connect(self.clear_list)
        h_box.addWidget(self.btn1)
        h_box.addWidget(btn2)
        h_box.addSpacerItem(
            QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Preferred))
        h_box.addWidget(btn3)
        # header.setLayout(h_box)
        # header.setFixedHeight(25)
        # v_box.addWidget(header)
        v_box.addLayout(h_box)
        self.setLayout(v_box)

        self._bold_font = QFont(self._hooks_list.font())
        self._bold_font.setBold(True)

        shortcut_addnative = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_N),
                                       self._app_window, self._on_addnative)
        shortcut_addnative.setAutoRepeat(False)

        shortcut_addjava = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_J),
                                     self._app_window, self._on_addjava)
        shortcut_addjava.setAutoRepeat(False)

        shortcut_add_native_on_load = QShortcut(
            QKeySequence(Qt.CTRL + Qt.Key_O), self._app_window,
            self._on_add_native_on_load)
        shortcut_add_native_on_load.setAutoRepeat(False)

        # new menu
        self.new_menu = QMenu('New')
        self.new_menu.addAction('Native', self._on_addnative)
        self.new_menu.addAction('Java', self._on_addjava)
        self.new_menu.addAction('Module loading', self._on_add_native_on_load)

    # ************************************************************************
    # **************************** Functions *********************************
    # ************************************************************************
    def delete_items(self):
        """ Delete selected Items
        """
        index = self._hooks_list.selectionModel().currentIndex().row()
        if index != -1:
            self._on_delete_hook(index)
            self._hooks_model.removeRow(index)

    def clear_list(self):
        """ Clear the List
        """
        # go through all items and tell it gets removed
        for item in range(self._hooks_model.rowCount()):
            self._on_delete_hook(item)

        if self._hooks_model.rowCount() > 0:
            # something was wrong it should be empty
            self._hooks_model.removeRows(0, self._hooks_model.rowCount())

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    def _on_add_hook(self, hook):
        type_ = QStandardItem()
        type_.setFont(self._bold_font)
        type_.setTextAlignment(Qt.AlignCenter)
        if hook.hook_type == HOOK_NATIVE:
            type_.setText('N')
            type_.setToolTip('Native hook')
        elif hook.hook_type == HOOK_JAVA:
            type_.setText('J')
            type_.setToolTip('Java hook')
        elif hook.hook_type == HOOK_ONLOAD:
            type_.setText('O')
            type_.setToolTip('On load hook')
        else:
            type_.setText('U')
            type_.setToolTip('Unknown Type')

        addr = QStandardItem()

        if hook.hook_type == HOOK_JAVA:
            parts = hook.get_input().split('.')
            addr.setText('.'.join(parts[:len(parts) - 1]))
        else:
            str_fmt = '0x{0:x}'
            if self._hooks_list.uppercase_hex:
                str_fmt = '0x{0:X}'
            # addr.setTextAlignment(Qt.AlignCenter)
            addr.setText(str_fmt.format(hook.get_ptr()))

        inp = QStandardItem()
        inp_text = hook.get_input()
        if hook.hook_type == HOOK_JAVA:
            parts = inp_text.split('.')
            inp_text = parts[len(parts) - 1]
        # if len(inp_text) > 15:
        #    inp_text = inp_text[:15] + '...'
        #    inp.setToolTip(hook.get_input())
        inp.setText(inp_text)
        inp.setData(hook.get_input(), Qt.UserRole + 2)
        inp.setToolTip(hook.get_input())

        logic = QStandardItem()
        logic.setTextAlignment(Qt.AlignCenter)
        logic.setFont(self._bold_font)
        if hook.logic and hook.logic != 'null' and hook.logic != 'undefined':
            logic.setText('ƒ')
            logic.setToolTip(hook.logic)
            logic.setData(hook.logic, Qt.UserRole + 2)

        condition = QStandardItem()
        condition.setTextAlignment(Qt.AlignCenter)
        condition.setFont(self._bold_font)
        if hook.condition and hook.condition != 'null' and hook.condition != 'undefined':
            condition.setText('ƒ')
            condition.setToolTip(hook.condition)
            condition.setData(hook.condition, Qt.UserRole + 2)

        self._hooks_model.appendRow([addr, type_, inp, logic, condition])

    def _on_hit_native_on_load(self, data):
        items = self._hooks_model.findItems(data[1]['module'], Qt.MatchExactly,
                                            2)
        if len(items) > 0:
            self._hooks_model.item(items[0].row(),
                                   0).setText(data[1]['moduleBase'])

    def _on_hit_java_on_load(self, data):
        items = self._hooks_model.findItems(data[0], Qt.MatchExactly, 2)
        if len(items) > 0:
            pass

    def _on_double_clicked(self, model_index):
        item = self._hooks_model.itemFromIndex(model_index)
        if model_index.column() == 3 and item.text() == 'ƒ':
            self._on_modify_logic(model_index.row())
        elif model_index.column() == 4 and item.text() == 'ƒ':
            self._on_modify_condition(model_index.row())
        else:
            self._app_window.jump_to_address(self._hooks_model.item(
                model_index.row(), 0).text(),
                                             view=1)

    def _on_context_menu(self, pos):
        context_menu = QMenu(self)
        context_menu.addMenu(self.new_menu)

        context_menu.addSeparator()
        index = self._hooks_list.indexAt(pos).row()
        if index != -1:
            context_menu.addAction(
                'Copy address', lambda: utils.copy_hex_to_clipboard(
                    self._hooks_model.item(index, 0).text()))
            context_menu.addAction(
                'Jump to address', lambda: self._app_window.jump_to_address(
                    self._hooks_model.item(index, 0).text()))
            context_menu.addSeparator()
            context_menu.addAction('Edit Logic',
                                   lambda: self._on_modify_logic(index))
            context_menu.addAction('Edit Condition',
                                   lambda: self._on_modify_condition(index))
            context_menu.addSeparator()
            context_menu.addAction('Delete Hook',
                                   lambda: self._on_delete_hook(index))

            if self._hooks_list.search_enabled:
                context_menu.addSeparator()
                context_menu.addAction('Search',
                                       self._hooks_list._on_cm_search)

        # show context menu
        global_pt = self._hooks_list.mapToGlobal(pos)
        context_menu.exec(global_pt)

    def _on_modify_logic(self, num_row):
        item = self._hooks_model.item(num_row, 3)
        data = item.data(Qt.UserRole + 2)
        if data is None:
            data = ''
        ptr = self._hooks_model.item(num_row, 0).text()
        accept, input_ = InputMultilineDialog().input('Insert logic for %s' %
                                                      ptr,
                                                      input_content=data)
        if accept:
            what = utils.parse_ptr(ptr)
            if what == 0:
                what = self._hooks_model.item(num_row, 2).data(Qt.UserRole + 2)
            if self._app_window.dwarf.dwarf_api(
                    'setHookLogic', [what, input_.replace('\n', '')]):
                item.setData(input_, Qt.UserRole + 2)
                if not item.text():
                    item.setText('ƒ')
                item.setToolTip(input_)
                self.onHookChanged.emit(ptr)

    def _on_modify_condition(self, num_row):
        item = self._hooks_model.item(num_row, 4)
        data = item.data(Qt.UserRole + 2)
        if data is None:
            data = ''
        ptr = self._hooks_model.item(num_row, 0).text()
        accept, input_ = InputDialog().input(self._app_window,
                                             'Insert condition for %s' % ptr,
                                             input_content=data)
        if accept:
            what = utils.parse_ptr(ptr)
            if what == 0:
                what = self._hooks_model.item(num_row, 2).data(Qt.UserRole + 2)
            if self._app_window.dwarf.dwarf_api('setHookCondition',
                                                [what, input_]):
                item.setData(input_, Qt.UserRole + 2)
                if not item.text():
                    item.setText('ƒ')
                item.setToolTip(input_)
                self.onHookChanged.emit(ptr)

    # + button
    def _on_additem_clicked(self):
        self.new_menu.exec_(QCursor.pos())

    # shortcuts/menu
    def _on_addnative(self):
        self._app_window.dwarf.hook_native()

    def _on_addjava(self):
        self._app_window.dwarf.hook_java()

    def _on_add_native_on_load(self):
        self._app_window.dwarf.hook_native_on_load()

    def _on_add_java_on_load(self):
        self._app_window.dwarf.hook_java_on_load()

    def _on_delete_hook(self, num_row):
        hook_type = self._hooks_model.item(num_row, 1).text()
        if hook_type == 'N':
            ptr = self._hooks_model.item(num_row, 0).text()
            ptr = utils.parse_ptr(ptr)
            self._app_window.dwarf.dwarf_api('deleteHook', ptr)
            self.onHookRemoved.emit(str(ptr))
        elif hook_type == 'J':
            input_ = self._hooks_model.item(num_row, 2).data(Qt.UserRole + 2)
            self._app_window.dwarf.dwarf_api('deleteHook', input_)
        elif hook_type == 'O':
            input_ = self._hooks_model.item(num_row, 2).data(Qt.UserRole + 2)
            self._app_window.dwarf.dwarf_api('deleteHook', input_)
        elif hook_type == 'U':
            ptr = self._hooks_model.item(num_row, 0).text()
            ptr = utils.parse_ptr(ptr)
            self._app_window.dwarf.dwarf_api('deleteHook', ptr)
            self.onHookRemoved.emit(str(ptr))

    def _on_hook_deleted(self, parts):
        _msg, _type, _val = parts

        additional = None

        if _type == 'java' or _type == 'java_on_load':
            _val = _val.split('.')
            str_frmt = '.'.join(_val[:-1])
            additional = _val[-1]
            item_index = 0
        elif _type == 'native_on_load':
            str_frmt = _val
            item_index = 2
        else:
            _ptr = utils.parse_ptr(_val)
            if self._hooks_list._uppercase_hex:
                str_frmt = '0x{0:X}'.format(_ptr)
            else:
                str_frmt = '0x{0:x}'.format(_ptr)
            item_index = 0

        for _item in range(self._hooks_model.rowCount()):
            item = self._hooks_model.item(_item, item_index)

            if item is None:
                continue

            if str_frmt == item.text():
                if additional is not None:
                    if additional == self._hooks_model.item(_item, 2).text():
                        self._hooks_model.removeRow(_item)
                else:
                    self._hooks_model.removeRow(_item)
class comics_project_manager_docker(DockWidget):
    setupDictionary = {}
    stringName = i18n("Comics Manager")
    projecturl = None

    def __init__(self):
        super().__init__()
        self.setWindowTitle(self.stringName)

        # Setup layout:
        base = QHBoxLayout()
        widget = QWidget()
        widget.setLayout(base)
        baseLayout = QSplitter()
        base.addWidget(baseLayout)
        self.setWidget(widget)
        buttonLayout = QVBoxLayout()
        buttonBox = QWidget()
        buttonBox.setLayout(buttonLayout)
        baseLayout.addWidget(buttonBox)

        # Comic page list and pages model
        self.comicPageList = QListView()
        self.comicPageList.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.comicPageList.setDragEnabled(True)
        self.comicPageList.setDragDropMode(QAbstractItemView.InternalMove)
        self.comicPageList.setDefaultDropAction(Qt.MoveAction)
        self.comicPageList.setAcceptDrops(True)
        self.comicPageList.setItemDelegate(comic_page_delegate())
        self.pagesModel = QStandardItemModel()
        self.comicPageList.doubleClicked.connect(self.slot_open_page)
        self.comicPageList.setIconSize(QSize(128, 128))
        # self.comicPageList.itemDelegate().closeEditor.connect(self.slot_write_description)
        self.pagesModel.layoutChanged.connect(self.slot_write_config)
        self.pagesModel.rowsInserted.connect(self.slot_write_config)
        self.pagesModel.rowsRemoved.connect(self.slot_write_config)
        self.pagesModel.rowsMoved.connect(self.slot_write_config)
        self.comicPageList.setModel(self.pagesModel)
        pageBox = QWidget()
        pageBox.setLayout(QVBoxLayout())
        zoomSlider = QSlider(Qt.Horizontal, None)
        zoomSlider.setRange(1, 8)
        zoomSlider.setValue(4)
        zoomSlider.setTickInterval(1)
        zoomSlider.setMinimumWidth(10)
        zoomSlider.valueChanged.connect(self.slot_scale_thumbnails)
        self.projectName = Elided_Text_Label()
        pageBox.layout().addWidget(self.projectName)
        pageBox.layout().addWidget(zoomSlider)
        pageBox.layout().addWidget(self.comicPageList)
        baseLayout.addWidget(pageBox)

        self.btn_project = QToolButton()
        self.btn_project.setPopupMode(QToolButton.MenuButtonPopup)
        self.btn_project.setSizePolicy(QSizePolicy.Minimum,
                                       QSizePolicy.Minimum)
        menu_project = QMenu()
        self.action_new_project = QAction(i18n("New Project"), self)
        self.action_new_project.triggered.connect(self.slot_new_project)
        self.action_load_project = QAction(i18n("Open Project"), self)
        self.action_load_project.triggered.connect(self.slot_open_config)
        menu_project.addAction(self.action_new_project)
        menu_project.addAction(self.action_load_project)
        self.btn_project.setMenu(menu_project)
        self.btn_project.setDefaultAction(self.action_load_project)
        buttonLayout.addWidget(self.btn_project)

        # Settings dropdown with actions for the different settings menus.
        self.btn_settings = QToolButton()
        self.btn_settings.setPopupMode(QToolButton.MenuButtonPopup)
        self.btn_settings.setSizePolicy(QSizePolicy.Minimum,
                                        QSizePolicy.Minimum)
        self.action_edit_project_settings = QAction(i18n("Project Settings"),
                                                    self)
        self.action_edit_project_settings.triggered.connect(
            self.slot_edit_project_settings)
        self.action_edit_meta_data = QAction(i18n("Meta Data"), self)
        self.action_edit_meta_data.triggered.connect(self.slot_edit_meta_data)
        self.action_edit_export_settings = QAction(i18n("Export Settings"),
                                                   self)
        self.action_edit_export_settings.triggered.connect(
            self.slot_edit_export_settings)
        menu_settings = QMenu()
        menu_settings.addAction(self.action_edit_project_settings)
        menu_settings.addAction(self.action_edit_meta_data)
        menu_settings.addAction(self.action_edit_export_settings)
        self.btn_settings.setDefaultAction(self.action_edit_project_settings)
        self.btn_settings.setMenu(menu_settings)
        buttonLayout.addWidget(self.btn_settings)
        self.btn_settings.setDisabled(True)

        # Add page drop down with different page actions.
        self.btn_add_page = QToolButton()
        self.btn_add_page.setPopupMode(QToolButton.MenuButtonPopup)
        self.btn_add_page.setSizePolicy(QSizePolicy.Minimum,
                                        QSizePolicy.Minimum)

        self.action_add_page = QAction(i18n("Add Page"), self)
        self.action_add_page.triggered.connect(self.slot_add_new_page_single)
        self.action_add_template = QAction(i18n("Add Page from Template"),
                                           self)
        self.action_add_template.triggered.connect(
            self.slot_add_new_page_from_template)
        self.action_add_existing = QAction(i18n("Add Existing Pages"), self)
        self.action_add_existing.triggered.connect(self.slot_add_page_from_url)
        self.action_remove_selected_page = QAction(i18n("Remove Page"), self)
        self.action_remove_selected_page.triggered.connect(
            self.slot_remove_selected_page)
        self.action_resize_all_pages = QAction(i18n("Batch Resize"), self)
        self.action_resize_all_pages.triggered.connect(self.slot_batch_resize)
        self.btn_add_page.setDefaultAction(self.action_add_page)
        self.action_show_page_viewer = QAction(i18n("View Page In Window"),
                                               self)
        self.action_show_page_viewer.triggered.connect(
            self.slot_show_page_viewer)
        self.action_scrape_authors = QAction(i18n("Scrape Author Info"), self)
        self.action_scrape_authors.setToolTip(
            i18n(
                "Search for author information in documents and add it to the author list. This does not check for duplicates."
            ))
        self.action_scrape_authors.triggered.connect(
            self.slot_scrape_author_list)
        self.action_scrape_translations = QAction(
            i18n("Scrape Text for Translation"), self)
        self.action_scrape_translations.triggered.connect(
            self.slot_scrape_translations)
        actionList = []
        menu_page = QMenu()
        actionList.append(self.action_add_page)
        actionList.append(self.action_add_template)
        actionList.append(self.action_add_existing)
        actionList.append(self.action_remove_selected_page)
        actionList.append(self.action_resize_all_pages)
        actionList.append(self.action_show_page_viewer)
        actionList.append(self.action_scrape_authors)
        actionList.append(self.action_scrape_translations)
        menu_page.addActions(actionList)
        self.btn_add_page.setMenu(menu_page)
        buttonLayout.addWidget(self.btn_add_page)
        self.btn_add_page.setDisabled(True)

        self.comicPageList.setContextMenuPolicy(Qt.ActionsContextMenu)
        self.comicPageList.addActions(actionList)

        # Export button that... exports.
        self.btn_export = QPushButton(i18n("Export Comic"))
        self.btn_export.clicked.connect(self.slot_export)
        buttonLayout.addWidget(self.btn_export)
        self.btn_export.setDisabled(True)

        self.btn_project_url = QPushButton(i18n("Copy Location"))
        self.btn_project_url.setToolTip(
            i18n(
                "Copies the path of the project to the clipboard. Useful for quickly copying to a file manager or the like."
            ))
        self.btn_project_url.clicked.connect(self.slot_copy_project_url)
        self.btn_project_url.setDisabled(True)
        buttonLayout.addWidget(self.btn_project_url)

        self.page_viewer_dialog = comics_project_page_viewer.comics_project_page_viewer(
        )

        Application.notifier().imageSaved.connect(
            self.slot_check_for_page_update)

        buttonLayout.addItem(
            QSpacerItem(0, 0, QSizePolicy.Minimum,
                        QSizePolicy.MinimumExpanding))

    """
    Open the config file and load the json file into a dictionary.
    """

    def slot_open_config(self):
        self.path_to_config = QFileDialog.getOpenFileName(
            caption=i18n("Please select the JSON comic config file."),
            filter=str(i18n("JSON files") + "(*.json)"))[0]
        if os.path.exists(self.path_to_config) is True:
            configFile = open(self.path_to_config,
                              "r",
                              newline="",
                              encoding="utf-16")
            self.setupDictionary = json.load(configFile)
            self.projecturl = os.path.dirname(str(self.path_to_config))
            configFile.close()
            self.load_config()

    """
    Further config loading.
    """

    def load_config(self):
        self.projectName.setMainText(
            text=str(self.setupDictionary["projectName"]))
        self.fill_pages()
        self.btn_settings.setEnabled(True)
        self.btn_add_page.setEnabled(True)
        self.btn_export.setEnabled(True)
        self.btn_project_url.setEnabled(True)

    """
    Fill the pages model with the pages from the pages list.
    """

    def fill_pages(self):
        self.loadingPages = True
        self.pagesModel.clear()
        pagesList = []
        if "pages" in self.setupDictionary.keys():
            pagesList = self.setupDictionary["pages"]
        progress = QProgressDialog()
        progress.setMinimum(0)
        progress.setMaximum(len(pagesList))
        progress.setWindowTitle(i18n("Loading Pages..."))
        for url in pagesList:
            absurl = os.path.join(self.projecturl, url)
            if (os.path.exists(absurl)):
                #page = Application.openDocument(absurl)
                page = zipfile.ZipFile(absurl, "r")
                thumbnail = QImage.fromData(page.read("preview.png"))
                pageItem = QStandardItem()
                dataList = self.get_description_and_title(
                    page.read("documentinfo.xml"))
                if (dataList[0].isspace() or len(dataList[0]) < 1):
                    dataList[0] = os.path.basename(url)
                pageItem.setText(dataList[0].replace("_", " "))
                pageItem.setDragEnabled(True)
                pageItem.setDropEnabled(False)
                pageItem.setEditable(False)
                pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail)))
                pageItem.setData(dataList[1], role=CPE.DESCRIPTION)
                pageItem.setData(url, role=CPE.URL)
                pageItem.setData(dataList[2], role=CPE.KEYWORDS)
                pageItem.setData(dataList[3], role=CPE.LASTEDIT)
                pageItem.setData(dataList[4], role=CPE.EDITOR)
                pageItem.setToolTip(url)
                page.close()
                self.pagesModel.appendRow(pageItem)
                progress.setValue(progress.value() + 1)
        progress.setValue(len(pagesList))
        self.loadingPages = False

    """
    Function that is triggered by the zoomSlider
    Resizes the thumbnails.
    """

    def slot_scale_thumbnails(self, multiplier=4):
        self.comicPageList.setIconSize(QSize(multiplier * 32, multiplier * 32))

    """
    Function that takes the documentinfo.xml and parses it for the title, subject and abstract tags,
    to get the title and description.
    
    @returns a stringlist with the name on 0 and the description on 1.
    """

    def get_description_and_title(self, string):
        xmlDoc = ET.fromstring(string)
        calligra = str("{http://www.calligra.org/DTD/document-info}")
        name = ""
        if ET.iselement(xmlDoc[0].find(calligra + 'title')):
            name = xmlDoc[0].find(calligra + 'title').text
            if name is None:
                name = " "
        desc = ""
        if ET.iselement(xmlDoc[0].find(calligra + 'subject')):
            desc = xmlDoc[0].find(calligra + 'subject').text
        if desc is None or desc.isspace() or len(desc) < 1:
            if ET.iselement(xmlDoc[0].find(calligra + 'abstract')):
                desc = xmlDoc[0].find(calligra + 'abstract').text
                if desc is not None:
                    if desc.startswith("<![CDATA["):
                        desc = desc[len("<![CDATA["):]
                    if desc.startswith("]]>"):
                        desc = desc[:-len("]]>")]
        keywords = ""
        if ET.iselement(xmlDoc[0].find(calligra + 'keyword')):
            keywords = xmlDoc[0].find(calligra + 'keyword').text
        date = ""
        if ET.iselement(xmlDoc[0].find(calligra + 'date')):
            date = xmlDoc[0].find(calligra + 'date').text
        author = []
        if ET.iselement(xmlDoc[1].find(calligra + 'creator-first-name')):
            string = xmlDoc[1].find(calligra + 'creator-first-name').text
            if string is not None:
                author.append(string)
        if ET.iselement(xmlDoc[1].find(calligra + 'creator-last-name')):
            string = xmlDoc[1].find(calligra + 'creator-last-name').text
            if string is not None:
                author.append(string)
        if ET.iselement(xmlDoc[1].find(calligra + 'full-name')):
            string = xmlDoc[1].find(calligra + 'full-name').text
            if string is not None:
                author.append(string)

        return [name, desc, keywords, date, " ".join(author)]

    """
    Scrapes authors from the author data in the document info and puts them into the author list.
    Doesn't check for duplicates.
    """

    def slot_scrape_author_list(self):
        listOfAuthors = []
        if "authorList" in self.setupDictionary.keys():
            listOfAuthors = self.setupDictionary["authorList"]
        if "pages" in self.setupDictionary.keys():
            for relurl in self.setupDictionary["pages"]:
                absurl = os.path.join(self.projecturl, relurl)
                page = zipfile.ZipFile(absurl, "r")
                xmlDoc = ET.fromstring(page.read("documentinfo.xml"))
                calligra = str("{http://www.calligra.org/DTD/document-info}")
                authorelem = xmlDoc.find(calligra + 'author')
                author = {}
                if ET.iselement(authorelem.find(calligra + 'full-name')):
                    author["nickname"] = str(
                        authorelem.find(calligra + 'full-name').text)

                if ET.iselement(
                        authorelem.find(calligra + 'creator-first-name')):
                    author["first-name"] = str(
                        authorelem.find(calligra + 'creator-first-name').text)

                if ET.iselement(authorelem.find(calligra + 'initial')):
                    author["initials"] = str(
                        authorelem.find(calligra + 'initial').text)

                if ET.iselement(authorelem.find(calligra +
                                                'creator-last-name')):
                    author["last-name"] = str(
                        authorelem.find(calligra + 'creator-last-name').text)

                if ET.iselement(authorelem.find(calligra + 'email')):
                    author["email"] = str(
                        authorelem.find(calligra + 'email').text)

                if ET.iselement(authorelem.find(calligra + 'contact')):
                    contact = authorelem.find(calligra + 'contact')
                    contactMode = contact.get("type")
                    if contactMode == "email":
                        author["email"] = str(contact.text)
                    if contactMode == "homepage":
                        author["homepage"] = str(contact.text)

                if ET.iselement(authorelem.find(calligra + 'position')):
                    author["role"] = str(
                        authorelem.find(calligra + 'position').text)
                listOfAuthors.append(author)
                page.close()
        self.setupDictionary["authorList"] = listOfAuthors

    """
    Edit the general project settings like the project name, concept, pages location, export location, template location, metadata
    """

    def slot_edit_project_settings(self):
        dialog = comics_project_settings_dialog.comics_project_details_editor(
            self.projecturl)
        dialog.setConfig(self.setupDictionary, self.projecturl)

        if dialog.exec_() == QDialog.Accepted:
            self.setupDictionary = dialog.getConfig(self.setupDictionary)
            self.slot_write_config()
            self.projectName.setMainText(
                str(self.setupDictionary["projectName"]))

    """
    This allows users to select existing pages and add them to the pages list. The pages are currently not copied to the pages folder. Useful for existing projects.
    """

    def slot_add_page_from_url(self):
        # get the pages.
        urlList = QFileDialog.getOpenFileNames(
            caption=i18n("Which existing pages to add?"),
            directory=self.projecturl,
            filter=str(i18n("Krita files") + "(*.kra)"))[0]

        # get the existing pages list.
        pagesList = []
        if "pages" in self.setupDictionary.keys():
            pagesList = self.setupDictionary["pages"]

        # And add each url in the url list to the pages list and the model.
        for url in urlList:
            if self.projecturl not in urlList:
                newUrl = os.path.join(self.projecturl,
                                      self.setupDictionary["pagesLocation"],
                                      os.path.basename(url))
                shutil.move(url, newUrl)
                url = newUrl
            relative = os.path.relpath(url, self.projecturl)
            if url not in pagesList:
                page = zipfile.ZipFile(url, "r")
                thumbnail = QImage.fromData(page.read("preview.png"))
                dataList = self.get_description_and_title(
                    page.read("documentinfo.xml"))
                if (dataList[0].isspace() or len(dataList[0]) < 1):
                    dataList[0] = os.path.basename(url)
                newPageItem = QStandardItem()
                newPageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail)))
                newPageItem.setDragEnabled(True)
                newPageItem.setDropEnabled(False)
                newPageItem.setEditable(False)
                newPageItem.setText(dataList[0].replace("_", " "))
                newPageItem.setData(dataList[1], role=CPE.DESCRIPTION)
                newPageItem.setData(relative, role=CPE.URL)
                newPageItem.setData(dataList[2], role=CPE.KEYWORDS)
                newPageItem.setData(dataList[3], role=CPE.LASTEDIT)
                newPageItem.setData(dataList[4], role=CPE.EDITOR)
                newPageItem.setToolTip(relative)
                page.close()
                self.pagesModel.appendRow(newPageItem)

    """
    Remove the selected page from the list of pages. This does not remove it from disk(far too dangerous).
    """

    def slot_remove_selected_page(self):
        index = self.comicPageList.currentIndex()
        self.pagesModel.removeRow(index.row())

    """
    This function adds a new page from the default template. If there's no default template, or the file does not exist, it will 
    show the create/import template dialog. It will remember the selected item as the default template.
    """

    def slot_add_new_page_single(self):
        templateUrl = "templatepage"
        templateExists = False

        if "singlePageTemplate" in self.setupDictionary.keys():
            templateUrl = self.setupDictionary["singlePageTemplate"]
        if os.path.exists(os.path.join(self.projecturl, templateUrl)):
            templateExists = True

        if templateExists is False:
            if "templateLocation" not in self.setupDictionary.keys():
                self.setupDictionary["templateLocation"] = os.path.relpath(
                    QFileDialog.getExistingDirectory(
                        caption=i18n("Where are the templates located?"),
                        options=QFileDialog.ShowDirsOnly), self.projecturl)

            templateDir = os.path.join(
                self.projecturl, self.setupDictionary["templateLocation"])
            template = comics_template_dialog.comics_template_dialog(
                templateDir)

            if template.exec_() == QDialog.Accepted:
                templateUrl = os.path.relpath(template.url(), self.projecturl)
                self.setupDictionary["singlePageTemplate"] = templateUrl
        if os.path.exists(os.path.join(self.projecturl, templateUrl)):
            self.add_new_page(templateUrl)

    """
    This function always asks for a template showing the new template window. This allows users to have multiple different
    templates created for back covers, spreads, other and have them accessible, while still having the convenience of a singular
    "add page" that adds a default.
    """

    def slot_add_new_page_from_template(self):
        if "templateLocation" not in self.setupDictionary.keys():
            self.setupDictionary["templateLocation"] = os.path.relpath(
                QFileDialog.getExistingDirectory(
                    caption=i18n("Where are the templates located?"),
                    options=QFileDialog.ShowDirsOnly), self.projecturl)

        templateDir = os.path.join(self.projecturl,
                                   self.setupDictionary["templateLocation"])
        template = comics_template_dialog.comics_template_dialog(templateDir)

        if template.exec_() == QDialog.Accepted:
            templateUrl = os.path.relpath(template.url(), self.projecturl)
            self.add_new_page(templateUrl)

    """
    This is the actual function that adds the template using the template url.
    It will attempt to name the new page projectName+number.
    """

    def add_new_page(self, templateUrl):

        # check for page list and or location.
        pagesList = []
        if "pages" in self.setupDictionary.keys():
            pagesList = self.setupDictionary["pages"]
        if not "pageNumber" in self.setupDictionary.keys():
            self.setupDictionary['pageNumber'] = 0

        if (str(self.setupDictionary["pagesLocation"]).isspace()):
            self.setupDictionary["pagesLocation"] = os.path.relpath(
                QFileDialog.getExistingDirectory(
                    caption=i18n("Where should the pages go?"),
                    options=QFileDialog.ShowDirsOnly), self.projecturl)

        # Search for the possible name.
        extraUnderscore = str()
        if str(self.setupDictionary["projectName"])[-1].isdigit():
            extraUnderscore = "_"
        self.setupDictionary['pageNumber'] += 1
        pageName = str(self.setupDictionary["projectName"]).replace(
            " ", "_") + extraUnderscore + str(
                format(self.setupDictionary['pageNumber'], "03d"))
        url = os.path.join(str(self.setupDictionary["pagesLocation"]),
                           pageName + ".kra")

        # open the page by opening the template and resaving it, or just opening it.
        absoluteUrl = os.path.join(self.projecturl, url)
        if (os.path.exists(absoluteUrl)):
            newPage = Application.openDocument(absoluteUrl)
        else:
            booltemplateExists = os.path.exists(
                os.path.join(self.projecturl, templateUrl))
            if booltemplateExists is False:
                templateUrl = os.path.relpath(
                    QFileDialog.getOpenFileName(
                        caption=i18n(
                            "Which image should be the basis the new page?"),
                        directory=self.projecturl,
                        filter=str(i18n("Krita files") + "(*.kra)"))[0],
                    self.projecturl)
            newPage = Application.openDocument(
                os.path.join(self.projecturl, templateUrl))
            newPage.waitForDone()
            newPage.setFileName(absoluteUrl)
            newPage.setName(pageName.replace("_", " "))
            newPage.save()
            newPage.waitForDone()

        # Get out the extra data for the standard item.
        newPageItem = QStandardItem()
        newPageItem.setIcon(
            QIcon(QPixmap.fromImage(newPage.thumbnail(256, 256))))
        newPageItem.setDragEnabled(True)
        newPageItem.setDropEnabled(False)
        newPageItem.setEditable(False)
        newPageItem.setText(pageName.replace("_", " "))
        newPageItem.setData("", role=CPE.DESCRIPTION)
        newPageItem.setData(url, role=CPE.URL)
        newPageItem.setData("", role=CPE.KEYWORDS)
        newPageItem.setData("", role=CPE.LASTEDIT)
        newPageItem.setData("", role=CPE.EDITOR)
        newPageItem.setToolTip(url)

        # close page document.
        while os.path.exists(absoluteUrl) is False:
            qApp.processEvents()

        newPage.close()

        # add item to page.
        self.pagesModel.appendRow(newPageItem)

    """
    Write to the json configuration file.
    This also checks the current state of the pages list.
    """

    def slot_write_config(self):

        # Don't load when the pages are still being loaded, otherwise we'll be overwriting our own pages list.
        if (self.loadingPages is False):
            print("CPMT: writing comic configuration...")

            # Generate a pages list from the pagesmodel.
            pagesList = []
            for i in range(self.pagesModel.rowCount()):
                index = self.pagesModel.index(i, 0)
                url = str(self.pagesModel.data(index, role=CPE.URL))
                if url not in pagesList:
                    pagesList.append(url)
            self.setupDictionary["pages"] = pagesList

            # Save to our json file.
            configFile = open(self.path_to_config,
                              "w",
                              newline="",
                              encoding="utf-16")
            json.dump(self.setupDictionary,
                      configFile,
                      indent=4,
                      sort_keys=True,
                      ensure_ascii=False)
            configFile.close()
            print("CPMT: done")

    """
    Open a page in the pagesmodel in Krita.
    """

    def slot_open_page(self, index):
        if index.column() is 0:
            # Get the absolute url from the relative one in the pages model.
            absoluteUrl = os.path.join(
                self.projecturl, str(self.pagesModel.data(index,
                                                          role=CPE.URL)))

            # Make sure the page exists.
            if os.path.exists(absoluteUrl):
                page = Application.openDocument(absoluteUrl)

                # Set the title to the filename if it was empty. It looks a bit neater.
                if page.name().isspace or len(page.name()) < 1:
                    page.setName(
                        str(self.pagesModel.data(index,
                                                 role=Qt.DisplayRole)).replace(
                                                     "_", " "))

                # Add views for the document so the user can use it.
                Application.activeWindow().addView(page)
                Application.setActiveDocument(page)
            else:
                print(
                    "CPMT: The page cannot be opened because the file doesn't exist:",
                    absoluteUrl)

    """
    Call up the metadata editor dialog. Only when the dialog is "Accepted" will the metadata be saved.
    """

    def slot_edit_meta_data(self):
        dialog = comics_metadata_dialog.comic_meta_data_editor()

        dialog.setConfig(self.setupDictionary)
        if (dialog.exec_() == QDialog.Accepted):
            self.setupDictionary = dialog.getConfig(self.setupDictionary)
            self.slot_write_config()

    """
    An attempt at making the description editable from the comic pages list.
    It is currently not working because ZipFile has no overwrite mechanism,
    and I don't have the energy to write one yet.
    """

    def slot_write_description(self, index):

        for row in range(self.pagesModel.rowCount()):
            index = self.pagesModel.index(row, 1)
            indexUrl = self.pagesModel.index(row, 0)
            absoluteUrl = os.path.join(
                self.projecturl,
                str(self.pagesModel.data(indexUrl, role=CPE.URL)))
            page = zipfile.ZipFile(absoluteUrl, "a")
            xmlDoc = ET.ElementTree()
            ET.register_namespace("",
                                  "http://www.calligra.org/DTD/document-info")
            location = os.path.join(self.projecturl, "documentinfo.xml")
            xmlDoc.parse(location)
            xmlroot = ET.fromstring(page.read("documentinfo.xml"))
            calligra = "{http://www.calligra.org/DTD/document-info}"
            aboutelem = xmlroot.find(calligra + 'about')
            if ET.iselement(aboutelem.find(calligra + 'subject')):
                desc = aboutelem.find(calligra + 'subject')
                desc.text = self.pagesModel.data(index, role=Qt.EditRole)
                xmlstring = ET.tostring(xmlroot,
                                        encoding='unicode',
                                        method='xml',
                                        short_empty_elements=False)
                page.writestr(zinfo_or_arcname="documentinfo.xml",
                              data=xmlstring)
                for document in Application.documents():
                    if str(document.fileName()) == str(absoluteUrl):
                        document.setDocumentInfo(xmlstring)
            page.close()

    """
    Calls up the export settings dialog. Only when accepted will the configuration be written.
    """

    def slot_edit_export_settings(self):
        dialog = comics_export_dialog.comic_export_setting_dialog()
        dialog.setConfig(self.setupDictionary)

        if (dialog.exec_() == QDialog.Accepted):
            self.setupDictionary = dialog.getConfig(self.setupDictionary)
            self.slot_write_config()

    """
    Export the comic. Won't work without export settings set.
    """

    def slot_export(self):

        #ensure there is a unique identifier
        if "uuid" not in self.setupDictionary.keys():
            uuid = str()
            if "acbfID" in self.setupDictionary.keys():
                uuid = str(self.setupDictionary["acbfID"])
            else:
                uuid = QUuid.createUuid().toString()
            self.setupDictionary["uuid"] = uuid

        exporter = comics_exporter.comicsExporter()
        exporter.set_config(self.setupDictionary, self.projecturl)
        exportSuccess = exporter.export()
        if exportSuccess:
            print(
                "CPMT: Export success! The files have been written to the export folder!"
            )
            QMessageBox.information(
                self, i18n("Export success"),
                i18n("The files have been written to the export folder."),
                QMessageBox.Ok)

    """
    Calls up the comics project setup wizard so users can create a new json file with the basic information.
    """

    def slot_new_project(self):
        setup = comics_project_setup_wizard.ComicsProjectSetupWizard()
        setup.showDialog()

    """
    This is triggered by any document save.
    It checks if the given url in in the pages list, and if so,
    updates the appropriate page thumbnail.
    This helps with the management of the pages, because the user
    will be able to see the thumbnails as a todo for the whole comic,
    giving a good overview over whether they still need to ink, color or
    the like for a given page, and it thus also rewards the user whenever
    they save.
    """

    def slot_check_for_page_update(self, url):
        if "pages" in self.setupDictionary.keys():
            relUrl = os.path.relpath(url, self.projecturl)
            if relUrl in self.setupDictionary["pages"]:
                index = self.pagesModel.index(
                    self.setupDictionary["pages"].index(relUrl), 0)
                if index.isValid():
                    pageItem = self.pagesModel.itemFromIndex(index)
                    page = zipfile.ZipFile(url, "r")
                    dataList = self.get_description_and_title(
                        page.read("documentinfo.xml"))
                    if (dataList[0].isspace() or len(dataList[0]) < 1):
                        dataList[0] = os.path.basename(url)
                    thumbnail = QImage.fromData(page.read("preview.png"))
                    pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail)))
                    pageItem.setText(dataList[0])
                    pageItem.setData(dataList[1], role=CPE.DESCRIPTION)
                    pageItem.setData(url, role=CPE.URL)
                    pageItem.setData(dataList[2], role=CPE.KEYWORDS)
                    pageItem.setData(dataList[3], role=CPE.LASTEDIT)
                    pageItem.setData(dataList[4], role=CPE.EDITOR)
                    self.pagesModel.setItem(index.row(), index.column(),
                                            pageItem)

    """
    Resize all the pages in the pages list.
    It will show a dialog with the options for resizing.
    Then, it will try to pop up a progress dialog while resizing.
    The progress dialog shows the remaining time and pages.
    """

    def slot_batch_resize(self):
        dialog = QDialog()
        dialog.setWindowTitle(i18n("Resize all Pages"))
        buttons = QDialogButtonBox(QDialogButtonBox.Ok
                                   | QDialogButtonBox.Cancel)
        buttons.accepted.connect(dialog.accept)
        buttons.rejected.connect(dialog.reject)
        sizesBox = comics_export_dialog.comic_export_resize_widget(
            "Scale", batch=True, fileType=False)
        exporterSizes = comics_exporter.sizesCalculator()
        dialog.setLayout(QVBoxLayout())
        dialog.layout().addWidget(sizesBox)
        dialog.layout().addWidget(buttons)

        if dialog.exec_() == QDialog.Accepted:
            progress = QProgressDialog(i18n("Resizing pages..."), str(), 0,
                                       len(self.setupDictionary["pages"]))
            progress.setWindowTitle(i18n("Resizing Pages"))
            progress.setCancelButton(None)
            timer = QElapsedTimer()
            timer.start()
            config = {}
            config = sizesBox.get_config(config)
            for p in range(len(self.setupDictionary["pages"])):
                absoluteUrl = os.path.join(self.projecturl,
                                           self.setupDictionary["pages"][p])
                progress.setValue(p)
                timePassed = timer.elapsed()
                if (p > 0):
                    timeEstimated = (len(self.setupDictionary["pages"]) -
                                     p) * (timePassed / p)
                    passedString = str(int(timePassed / 60000)) + ":" + format(
                        int(timePassed / 1000), "02d") + ":" + format(
                            timePassed % 1000, "03d")
                    estimatedString = str(int(
                        timeEstimated / 60000)) + ":" + format(
                            int(timeEstimated / 1000), "02d") + ":" + format(
                                int(timeEstimated % 1000), "03d")
                    progress.setLabelText(
                        str(
                            i18n(
                                "{pages} of {pagesTotal} done. \nTime passed: {passedString}:\n Estimated:{estimated}"
                            )).format(pages=p,
                                      pagesTotal=len(
                                          self.setupDictionary["pages"]),
                                      passedString=passedString,
                                      estimated=estimatedString))
                    qApp.processEvents()
                if os.path.exists(absoluteUrl):
                    doc = Application.openDocument(absoluteUrl)
                    listScales = exporterSizes.get_scale_from_resize_config(
                        config["Scale"], [
                            doc.width(),
                            doc.height(),
                            doc.resolution(),
                            doc.resolution()
                        ])
                    doc.scaleImage(listScales[0], listScales[1], listScales[2],
                                   listScales[3], "bicubic")
                    doc.waitForDone()
                    doc.save()
                    doc.waitForDone()
                    doc.close()

    def slot_show_page_viewer(self):
        index = int(self.comicPageList.currentIndex().row())
        self.page_viewer_dialog.load_comic(self.path_to_config)
        self.page_viewer_dialog.go_to_page_index(index)
        self.page_viewer_dialog.show()

    """
    Function to copy the current project location into the clipboard.
    This is useful for users because they'll be able to use that url to quickly
    move to the project location in outside applications.
    """

    def slot_copy_project_url(self):
        if self.projecturl is not None:
            clipboard = qApp.clipboard()
            clipboard.setText(str(self.projecturl))

    """
    Scrape text files with the textlayer keys for text, and put those in a POT
    file. This makes it possible to handle translations.
    """

    def slot_scrape_translations(self):
        translationFolder = self.setupDictionary.get("translationLocation",
                                                     "translations")
        fullTranslationPath = os.path.join(self.projecturl, translationFolder)
        os.makedirs(fullTranslationPath, exist_ok=True)
        textLayersToSearch = self.setupDictionary.get("textLayerNames",
                                                      ["text"])

        scraper = comics_project_translation_scraper.translation_scraper(
            self.projecturl, translationFolder, textLayersToSearch,
            self.setupDictionary["projectName"])
        # Run text scraper.
        language = self.setupDictionary.get("language", "en")
        metadata = {}
        metadata["title"] = self.setupDictionary.get("title", "")
        metadata["summary"] = self.setupDictionary.get("summary", "")
        metadata["keywords"] = ", ".join(
            self.setupDictionary.get("otherKeywords", [""]))
        metadata["transnotes"] = self.setupDictionary.get(
            "translatorHeader", "Translator's Notes")
        scraper.start(self.setupDictionary["pages"], language, metadata)
        QMessageBox.information(
            self, i18n("Scraping success"),
            str(i18n("POT file has been written to: {file}")).format(
                file=fullTranslationPath), QMessageBox.Ok)

    """
    This is required by the dockwidget class, otherwise unused.
    """

    def canvasChanged(self, canvas):
        pass
Example #26
0
class QmyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)  #调用父类构造函数,创建窗体
        self.ui = Ui_MainWindow()  #创建UI对象
        self.ui.setupUi(self)  #构造UI界面

        self.setCentralWidget(self.ui.splitter)
        self.__buildStatusBar()

        self.COL_COUNT = 6  #常数,列数=6
        self.itemModel = QStandardItemModel(5, self.COL_COUNT,
                                            self)  # 数据模型,10行6列

        ##      headerList=["测深(m)","垂深(m)","方位(°)","总位移(m)","固井质量","测井取样"]
        ##      self.itemModel.setHorizontalHeaderLabels(headerList) #设置表头文字

        self.selectionModel = QItemSelectionModel(self.itemModel)  #Item选择模型
        self.selectionModel.currentChanged.connect(self.do_currentChanged)
        self.__lastColumnFlags = (Qt.ItemIsSelectable | Qt.ItemIsUserCheckable
                                  | Qt.ItemIsEnabled)
        self.__lastColumnTitle = "测井取样"

        #为tableView设置数据模型
        self.ui.tableView.setModel(self.itemModel)  #设置数据模型
        self.ui.tableView.setSelectionModel(self.selectionModel)  #设置选择模型

        oneOrMore = QAbstractItemView.ExtendedSelection
        self.ui.tableView.setSelectionMode(oneOrMore)  #可多选

        itemOrRow = QAbstractItemView.SelectItems
        self.ui.tableView.setSelectionBehavior(itemOrRow)  #单元格选择

        self.ui.tableView.verticalHeader().setDefaultSectionSize(22)  #缺省行高
        self.ui.tableView.setEnabled(False)  #先禁用tableView
        ##      self.__resetTable(5)

        #创建自定义代理组件并设置
        self.spinCeShen = QmyFloatSpinDelegate(0, 10000, 0, self)  #用于 测深
        self.spinLength = QmyFloatSpinDelegate(0, 6000, 2, self)  #垂深,总位移
        self.spinDegree = QmyFloatSpinDelegate(0, 360, 1, self)  #用于 方位

        self.ui.tableView.setItemDelegateForColumn(0, self.spinCeShen)  #测深
        self.ui.tableView.setItemDelegateForColumn(1, self.spinLength)  #垂深
        self.ui.tableView.setItemDelegateForColumn(3, self.spinLength)  #总位移
        self.ui.tableView.setItemDelegateForColumn(2, self.spinDegree)  #方位角

        qualities = ["优", "良", "合格", "不合格"]
        self.comboDelegate = QmyComboBoxDelegate(self)
        self.comboDelegate.setItems(qualities, False)  #不可编辑
        self.ui.tableView.setItemDelegateForColumn(4,
                                                   self.comboDelegate)  #固井质量

##  ==============自定义功能函数============

    def __buildStatusBar(self):  ##构建状态栏
        self.LabCellPos = QLabel("当前单元格:", self)
        self.LabCellPos.setMinimumWidth(180)
        self.ui.statusBar.addWidget(self.LabCellPos)

        self.LabCellText = QLabel("单元格内容:", self)
        self.LabCellText.setMinimumWidth(150)
        self.ui.statusBar.addWidget(self.LabCellText)

        self.LabCurFile = QLabel("当前文件:", self)
        self.ui.statusBar.addPermanentWidget(self.LabCurFile)

##   def __resetTable(self,tableRowCount):  #复位数据表,必须为数值设置0,否则代理组件出错
##      self.itemModel.removeRows(0,self.itemModel.rowCount()) #删除所有行
##      self.itemModel.setRowCount(tableRowCount)     #设置新的行数
##
##      for i in range(tableRowCount):  #设置最后一列
##         for j in range(self.COL_COUNT-1):
##            index=self.itemModel.index(i,j) #获取模型索引
##            item=self.itemModel.itemFromIndex(index) #获取item
##            item.setData(0,Qt.DisplayRole) #数值必须初始化为0
##            item.setTextAlignment(Qt.AlignHCenter)
##
##         index=self.itemModel.index(i,self.COL_COUNT-1) #获取模型索引
##         item=self.itemModel.itemFromIndex(index) #获取item
##         item.setCheckable(True)
##         item.setData(self.__lastColumnTitle,Qt.DisplayRole)
##         item.setEditable(False) #不可编辑

    def __iniModelFromStringList(self, allLines):  ##从字符串列表构建模型
        rowCnt = len(allLines)  #文本行数,第1行是标题
        self.itemModel.setRowCount(rowCnt - 1)  #实际数据行数

        headerText = allLines[0].strip()  #第1行是表头,去掉末尾的换行符 "\n"
        headerList = headerText.split("\t")
        self.itemModel.setHorizontalHeaderLabels(headerList)

        self.__lastColumnTitle = headerList[len(headerList) -
                                            1]  # 最后一列表头的标题,即“测井取样”

        lastColNo = self.COL_COUNT - 1  #最后一列的列号
        for i in range(rowCnt - 1):
            lineText = allLines[i + 1].strip()  #一行的文字,\t分隔
            strList = lineText.split("\t")
            for j in range(self.COL_COUNT - 1):  #不含最后一列
                item = QStandardItem(strList[j])
                self.itemModel.setItem(i, j, item)

            item = QStandardItem(self.__lastColumnTitle)  #最后一列
            item.setFlags(self.__lastColumnFlags)
            item.setCheckable(True)
            if (strList[lastColNo] == "0"):
                item.setCheckState(Qt.Unchecked)
            else:
                item.setCheckState(Qt.Checked)

            self.itemModel.setItem(i, lastColNo, item)  #设置最后一列的item

    def __setCellAlignment(self, align=Qt.AlignHCenter):
        if (not self.selectionModel.hasSelection()):  #没有选择的项
            return
        selectedIndex = self.selectionModel.selectedIndexes()  #列表类型
        count = len(selectedIndex)
        for i in range(count):
            index = selectedIndex[i]  #获取其中的一个模型索引
            item = self.itemModel.itemFromIndex(index)  #获取一个单元格的项数据对象
            item.setTextAlignment(align)  #设置文字对齐方式

##  ==========由connectSlotsByName() 自动连接的槽函数==================

    @pyqtSlot()  ##“打开文件”
    def on_actOpen_triggered(self):
        curPath = os.getcwd()  #获取当前路径
        filename, flt = QFileDialog.getOpenFileName(
            self, "打开一个文件", curPath, "井斜数据文件(*.txt);;所有文件(*.*)")
        if (filename == ""):
            return

        self.LabCurFile.setText("当前文件:" + filename)
        self.ui.plainTextEdit.clear()

        aFile = open(filename, 'r')
        allLines = aFile.readlines()  #读取所有行,list类型,每行末尾带有 \n
        ##        for eachLine in aFile:  #每次读取一行
        ##            self.ui.plainTextEdit.appendPlainText(eachLine)
        aFile.close()

        for strLine in allLines:
            self.ui.plainTextEdit.appendPlainText(strLine.strip())

        self.__iniModelFromStringList(allLines)
        self.ui.actAppend.setEnabled(True)  #更新Actions的enable属性
        self.ui.actInsert.setEnabled(True)
        self.ui.actDelete.setEnabled(True)
        self.ui.actSave.setEnabled(True)
        self.ui.actModelData.setEnabled(True)

        self.ui.tableView.setEnabled(True)  #启用tableView

    @pyqtSlot()  ##保存文件
    def on_actSave_triggered(self):
        curPath = os.getcwd()  #获取当前路径
        filename, flt = QFileDialog.getSaveFileName(
            self, "保存文件", curPath, "井斜数据文件(*.txt);;所有文件(*.*)")
        if (filename == ""):
            return

        self.on_actModelData_triggered()  #更新数据到plainTextEdit

        aFile = open(filename, 'w')  #以写方式打开
        aFile.write(self.ui.plainTextEdit.toPlainText())
        aFile.close()

    @pyqtSlot()
    def on_actAppend_triggered(self):  #在最后添加一行
        itemlist = []  # 列表
        for i in range(self.COL_COUNT - 1):
            item = QStandardItem("0")
            itemlist.append(item)

        item = QStandardItem(self.__lastColumnTitle)  #最后一列
        item.setCheckable(True)
        item.setFlags(self.__lastColumnFlags)
        itemlist.append(item)

        self.itemModel.appendRow(itemlist)
        curIndex = self.itemModel.index(self.itemModel.rowCount() - 1, 0)
        self.selectionModel.clearSelection()
        self.selectionModel.setCurrentIndex(curIndex,
                                            QItemSelectionModel.Select)

    @pyqtSlot()  ##插入一行
    def on_actInsert_triggered(self):
        itemlist = []  # 列表
        for i in range(self.COL_COUNT - 1):
            item = QStandardItem("0")
            itemlist.append(item)

        item = QStandardItem(self.__lastColumnTitle)  #最后一列
        item.setFlags(self.__lastColumnFlags)
        item.setCheckable(True)
        item.setCheckState(Qt.Checked)
        itemlist.append(item)

        curIndex = self.selectionModel.currentIndex()
        #获取当前选中项的模型索引
        self.itemModel.insertRow(curIndex.row(), itemlist)
        #在当前行的前面插入一行
        self.selectionModel.clearSelection()
        self.selectionModel.setCurrentIndex(curIndex,
                                            QItemSelectionModel.Select)

    @pyqtSlot()  ##删除当前行
    def on_actDelete_triggered(self):
        curIndex = self.selectionModel.currentIndex()  #获取当前选择单元格的模型索引
        self.itemModel.removeRow(curIndex.row())  # //删除当前行

    @pyqtSlot()  ##左对齐
    def on_actAlignLeft_triggered(self):
        self.__setCellAlignment(Qt.AlignLeft | Qt.AlignVCenter)

    @pyqtSlot()  ##中间对齐
    def on_actAlignCenter_triggered(self):
        self.__setCellAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

    @pyqtSlot()  ##右对齐
    def on_actAlignRight_triggered(self):
        self.__setCellAlignment(Qt.AlignRight | Qt.AlignVCenter)

    @pyqtSlot(bool)  ##字体Bold
    def on_actFontBold_triggered(self, checked):
        if (not self.selectionModel.hasSelection()):  #没有选择的项
            return

        selectedIndex = self.selectionModel.selectedIndexes()  #列表类型
        count = len(selectedIndex)
        for i in range(count):
            index = selectedIndex[i]  #获取其中的一个模型索引
            item = self.itemModel.itemFromIndex(index)  #获取一个单元格的项数据对象
            font = item.font()
            font.setBold(checked)
            item.setFont(font)

    @pyqtSlot()  ##模型数据显示到plainTextEdit里
    def on_actModelData_triggered(self):
        self.ui.plainTextEdit.clear()
        lineStr = ""
        for i in range(self.itemModel.columnCount() - 1):  #表头
            item = self.itemModel.horizontalHeaderItem(i)
            lineStr = lineStr + item.text() + "\t"
        item = self.itemModel.horizontalHeaderItem(self.COL_COUNT - 1)  #最后一列
        lineStr = lineStr + item.text()
        self.ui.plainTextEdit.appendPlainText(lineStr)

        for i in range(self.itemModel.rowCount()):
            lineStr = ""
            for j in range(self.itemModel.columnCount() - 1):  #不包括最后一列
                item = self.itemModel.item(i, j)
                lineStr = lineStr + item.text() + "\t"

            item = self.itemModel.item(i, self.COL_COUNT - 1)  #最后一列
            if (item.checkState() == Qt.Checked):
                lineStr = lineStr + "1"
            else:
                lineStr = lineStr + "0"
            self.ui.plainTextEdit.appendPlainText(lineStr)

##  =============自定义槽函数===============================

    def do_currentChanged(self, current, previous):
        if (current != None):  #当前模型索引有效
            self.LabCellPos.setText(
                "当前单元格:%d行,%d列" %
                (current.row(), current.column()))  #显示模型索引的行和列号
            item = self.itemModel.itemFromIndex(current)  #从模型索引获得Item
            self.LabCellText.setText("单元格内容:" + item.text())  #显示item的文字内容

            font = item.font()  #获取item的字体
            self.ui.actFontBold.setChecked(font.bold())  #更新actFontBold的check状态
Example #27
0
class WatchpointsWidget(QWidget):
    """ WatchpointsWidget

        Signals:
            onItemSelected(addr_str) - item dblclicked
            onItemAddClick

        Constants:
            MEMORY_ACCESS_READ = 1
            MEMORY_ACCESS_WRITE = 2
            MEMORY_ACCESS_EXECUTE = 4
            MEMORY_WATCH_SINGLESHOT = 8
    """
    MEMORY_ACCESS_READ = 1
    MEMORY_ACCESS_WRITE = 2
    MEMORY_ACCESS_EXECUTE = 4
    MEMORY_WATCH_SINGLESHOT = 8

    onItemDoubleClicked = pyqtSignal(int, name='onItemDoubleClicked')
    onItemAdded = pyqtSignal(int, name='onItemAdded')
    onItemRemoved = pyqtSignal(int, name='onItemRemoved')

    def __init__(self, parent=None):  # pylint: disable=too-many-statements
        super(WatchpointsWidget, self).__init__(parent=parent)
        self._app_window = parent

        if self._app_window.dwarf is None:
            print('WatchpointsWidget created before Dwarf exists')
            return

        self._uppercase_hex = True
        self.setAutoFillBackground(True)

        # connect to dwarf
        self._app_window.dwarf.onWatchpointAdded.connect(self._on_watchpoint_added)
        self._app_window.dwarf.onWatchpointRemoved.connect(
            self._on_watchpoint_removed)

        # setup our model
        self._watchpoints_model = QStandardItemModel(0, 5)
        self._watchpoints_model.setHeaderData(0, Qt.Horizontal, 'Address')
        self._watchpoints_model.setHeaderData(1, Qt.Horizontal, 'R')
        self._watchpoints_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter,
                                           Qt.TextAlignmentRole)
        self._watchpoints_model.setHeaderData(2, Qt.Horizontal, 'W')
        self._watchpoints_model.setHeaderData(2, Qt.Horizontal, Qt.AlignCenter,
                                           Qt.TextAlignmentRole)
        self._watchpoints_model.setHeaderData(3, Qt.Horizontal, 'X')
        self._watchpoints_model.setHeaderData(3, Qt.Horizontal, Qt.AlignCenter,
                                           Qt.TextAlignmentRole)
        self._watchpoints_model.setHeaderData(4, Qt.Horizontal, 'S')
        self._watchpoints_model.setHeaderData(4, Qt.Horizontal, Qt.AlignCenter,
                                           Qt.TextAlignmentRole)

        # setup ui
        v_box = QVBoxLayout(self)
        v_box.setContentsMargins(0, 0, 0, 0)
        self.list_view = DwarfListView()
        self.list_view.setModel(self._watchpoints_model)
        self.list_view.header().setSectionResizeMode(0, QHeaderView.Stretch)
        self.list_view.header().setSectionResizeMode(
            1, QHeaderView.ResizeToContents | QHeaderView.Fixed)
        self.list_view.header().setSectionResizeMode(
            2, QHeaderView.ResizeToContents | QHeaderView.Fixed)
        self.list_view.header().setSectionResizeMode(
            3, QHeaderView.ResizeToContents | QHeaderView.Fixed)
        self.list_view.header().setSectionResizeMode(
            4, QHeaderView.ResizeToContents | QHeaderView.Fixed)
        self.list_view.header().setStretchLastSection(False)
        self.list_view.doubleClicked.connect(self._on_item_dblclick)
        self.list_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.list_view.customContextMenuRequested.connect(self._on_contextmenu)

        v_box.addWidget(self.list_view)
        #header = QHeaderView(Qt.Horizontal, self)

        h_box = QHBoxLayout()
        h_box.setContentsMargins(5, 2, 5, 5)
        btn1 = QPushButton(
            QIcon(utils.resource_path('assets/icons/plus.svg')), '')
        btn1.setFixedSize(20, 20)
        btn1.clicked.connect(self._on_additem_clicked)
        btn2 = QPushButton(
            QIcon(utils.resource_path('assets/icons/dash.svg')), '')
        btn2.setFixedSize(20, 20)
        btn2.clicked.connect(self.delete_items)
        btn3 = QPushButton(
            QIcon(utils.resource_path('assets/icons/trashcan.svg')), '')
        btn3.setFixedSize(20, 20)
        btn3.clicked.connect(self.clear_list)
        h_box.addWidget(btn1)
        h_box.addWidget(btn2)
        h_box.addSpacerItem(
            QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Preferred))
        h_box.addWidget(btn3)
        # header.setLayout(h_box)
        # header.setFixedHeight(25)
        # v_box.addWidget(header)
        v_box.addLayout(h_box)

        # create a centered dot icon
        _section_width = self.list_view.header().sectionSize(2)
        self._new_pixmap = QPixmap(_section_width, 20)
        self._new_pixmap.fill(Qt.transparent)
        painter = QPainter(self._new_pixmap)
        rect = QRect((_section_width * 0.5), 0, 20, 20)
        painter.setBrush(QColor('#666'))
        painter.setPen(QColor('#666'))
        painter.drawEllipse(rect)
        self._dot_icon = QIcon(self._new_pixmap)

        # shortcuts
        shortcut_add = QShortcut(
            QKeySequence(Qt.CTRL + Qt.Key_W), self._app_window,
            self._on_additem_clicked)
        shortcut_add.setAutoRepeat(False)

        self.setLayout(v_box)

    # ************************************************************************
    # **************************** Properties ********************************
    # ************************************************************************
    @property
    def uppercase_hex(self):
        """ Addresses displayed lower/upper-case
        """
        return self._uppercase_hex

    @uppercase_hex.setter
    def uppercase_hex(self, value):
        """ Addresses displayed lower/upper-case
            value - bool or str
                    'upper', 'lower'
        """
        if isinstance(value, bool):
            self._uppercase_hex = value
        elif isinstance(value, str):
            self._uppercase_hex = (value == 'upper')

    # ************************************************************************
    # **************************** Functions *********************************
    # ************************************************************************
    def do_addwatchpoint_dlg(self, ptr=None):  # pylint: disable=too-many-branches
        """ Shows AddWatchpointDialog
        """
        watchpoint_dlg = AddWatchpointDialog(self, ptr)
        if watchpoint_dlg.exec_() == QDialog.Accepted:
            mem_r = watchpoint_dlg.acc_read.isChecked()
            mem_w = watchpoint_dlg.acc_write.isChecked()
            mem_x = watchpoint_dlg.acc_execute.isChecked()
            mem_s = watchpoint_dlg.singleshot.isChecked()

            ptr = watchpoint_dlg.text_field.toPlainText()

            if ptr:
                if isinstance(ptr, str):
                    if ptr.startswith('0x') or ptr.startswith('#'):
                        ptr = utils.parse_ptr(ptr)
                    else:
                        try:
                            ptr = int(ptr, 10)
                        except ValueError:
                            pass

                    # int now?
                    if not isinstance(ptr, int):
                        try:
                            ptr = int(
                                self._app_window.dwarf.dwarf_api(
                                    'evaluatePtr', ptr), 16)
                        except ValueError:
                            ptr = 0

                        if ptr == 0:
                            return

                        if not self._app_window.dwarf.dwarf_api(
                                'isValidPointer', ptr):
                            return
                else:
                    return

                mem_val = 0
                if mem_r:
                    mem_val |= self.MEMORY_ACCESS_READ
                if mem_w:
                    mem_val |= self.MEMORY_ACCESS_WRITE
                if mem_x:
                    mem_val |= self.MEMORY_ACCESS_EXECUTE
                if mem_s:
                    mem_val |= self.MEMORY_WATCH_SINGLESHOT

                self.add_address(ptr, mem_val, from_api=False)

                # return [ptr, mem_val]

    def add_address(self, ptr, flags, from_api=False):
        """ Adds Address to display

            ptr - str or int
            flags - int
        """
        if isinstance(ptr, str):
            ptr = utils.parse_ptr(ptr)

        if not isinstance(flags, int):
            try:
                flags = int(flags, 10)
            except ValueError:
                flags = 3

        if not from_api:
            # function was called directly so add it to dwarf
            if not self._app_window.dwarf.is_address_watched(ptr):
                self._app_window.dwarf.dwarf_api('putWatchpoint', [ptr, flags])
                return

        # show header
        self.list_view.setHeaderHidden(False)

        # create items to add
        if self._uppercase_hex:
            str_frmt = '0x{0:X}'
        else:
            str_frmt = '0x{0:x}'

        addr = QStandardItem()
        addr.setText(str_frmt.format(ptr))

        read = QStandardItem()
        write = QStandardItem()
        execute = QStandardItem()
        singleshot = QStandardItem()

        if flags & self.MEMORY_ACCESS_READ:
            read.setIcon(self._dot_icon)
        if flags & self.MEMORY_ACCESS_WRITE:
            write.setIcon(self._dot_icon)
        if flags & self.MEMORY_ACCESS_EXECUTE:
            execute.setIcon(self._dot_icon)
        if flags & self.MEMORY_WATCH_SINGLESHOT:
            singleshot.setIcon(self._dot_icon)

        # add items as new row on top
        self._watchpoints_model.insertRow(
            0, [addr, read, write, execute, singleshot])

    def remove_address(self, ptr, from_api=False):
        """ Remove Address from List
        """
        if isinstance(ptr, str):
            ptr = utils.parse_ptr(ptr)

        if not from_api:
            # called somewhere so remove watchpoint in dwarf too
            self._app_window.dwarf.dwarf_api('removeWatchpoint', ptr)
            return

        str_frmt = ''
        if self._uppercase_hex:
            str_frmt = '0x{0:X}'.format(ptr)
        else:
            str_frmt = '0x{0:x}'.format(ptr)

        model = self.list_view.model()
        for item in range(model.rowCount()):
            if str_frmt == model.item(item).text():
                model.removeRow(item)

    def delete_items(self):
        """ Delete selected Items
        """
        model = self.list_view.model()

        index = self.list_view.selectionModel().currentIndex().row()
        if index != -1:
            ptr = model.item(index, 0).text()
            self.remove_address(ptr)

    def clear_list(self):
        """ Clear the List
        """
        model = self.list_view.model()

        # go through all items and tell it gets removed
        for item in range(model.rowCount()):
            ptr = model.item(item, 0).text()
            self.remove_address(ptr)

        if model.rowCount() > 0:
            # something was wrong it should be empty
            model.removeRows(0, model.rowCount())

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    def _on_contextmenu(self, pos):
        index = self.list_view.indexAt(pos).row()
        glbl_pt = self.list_view.mapToGlobal(pos)
        context_menu = QMenu(self)
        context_menu.addAction('Add watchpoint', self._on_additem_clicked)
        if index != -1:
            context_menu.addSeparator()
            context_menu.addAction(
                'Copy address', lambda: utils.copy_hex_to_clipboard(
                    self._watchpoints_model.item(index, 0).text()))
            context_menu.addAction(
                'Jump to address', lambda: self._app_window.jump_to_address(
                    self._watchpoints_model.item(index, 0).text()))
            context_menu.addAction(
                'Delete watchpoint', lambda: self.remove_address(
                    self._watchpoints_model.item(index, 0).text()))
            if self.list_view.search_enabled:
                context_menu.addSeparator()
                context_menu.addAction('Search', self.list_view._on_cm_search)
        context_menu.exec_(glbl_pt)

    def _on_item_dblclick(self, model_index):
        row = self._watchpoints_model.itemFromIndex(model_index).row()
        if row != -1:
            ptr = self._watchpoints_model.item(row, 0).text()
            self.onItemDoubleClicked.emit(ptr)

    def _on_additem_clicked(self):
        if self._app_window.dwarf.pid == 0:
            return

        self.do_addwatchpoint_dlg()

    def _on_watchpoint_added(self, watchpoint):
        """ Callback from Dwarf after Watchpoint is added
        """
        # add to watchpointslist
        self.add_address(watchpoint.address, watchpoint.flags, from_api=True)
        self.onItemAdded.emit(watchpoint.address)

    def _on_watchpoint_removed(self, ptr):
        """ Callback from Dwarf after watchpoint is removed
        """
        ptr = utils.parse_ptr(ptr)
        # remove from list
        self.remove_address(ptr, from_api=True)
        self.onItemRemoved.emit(ptr)
Example #28
0
class JavaTracePanel(QWidget):
    def __init__(self, app, *__args):
        super().__init__(app)
        self.app = app

        self.app.dwarf.onJavaTraceEvent.connect(self.on_event)
        self.app.dwarf.onEnumerateJavaClassesStart.connect(
            self.on_enumeration_start)
        self.app.dwarf.onEnumerateJavaClassesMatch.connect(
            self.on_enumeration_match)
        self.app.dwarf.onEnumerateJavaClassesComplete.connect(
            self.on_enumeration_complete)

        self.tracing = False
        self.trace_classes = []
        self.trace_depth = 0

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        self._record_icon = QIcon(
            utils.resource_path('assets/icons/record.png'))
        self._pause_icon = QIcon(utils.resource_path('assets/icons/pause.png'))
        self._stop_icon = QIcon(utils.resource_path('assets/icons/stop.png'))

        self._tool_bar = QToolBar()
        self._tool_bar.addAction('Start', self.start_trace)
        self._tool_bar.addAction('Pause', self.pause_trace)
        self._tool_bar.addAction('Stop', self.stop_trace)
        self._tool_bar.addSeparator()
        self._entries_lbl = QLabel('Entries: 0')
        self._entries_lbl.setStyleSheet('color: #ef5350;')
        self._entries_lbl.setContentsMargins(10, 0, 10, 2)
        self._entries_lbl.setAttribute(Qt.WA_TranslucentBackground,
                                       True)  # keep this
        self._entries_lbl.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self._entries_lbl.setSizePolicy(QSizePolicy.Expanding,
                                        QSizePolicy.Preferred)
        self._tool_bar.addWidget(self._entries_lbl)

        layout.addWidget(self._tool_bar)

        self.setup_splitter = QSplitter()
        self.events_list = JavaTraceView(self)
        self.events_list.setVisible(False)

        self.trace_list = DwarfListView()
        self.trace_list_model = QStandardItemModel(0, 1)
        self.trace_list_model.setHeaderData(0, Qt.Horizontal, 'Traced')
        self.trace_list.setModel(self.trace_list_model)

        self.trace_list.doubleClicked.connect(self.trace_list_double_click)

        self.class_list = DwarfListView()
        self.class_list_model = QStandardItemModel(0, 1)
        self.class_list_model.setHeaderData(0, Qt.Horizontal, 'Classes')
        self.class_list.setModel(self.class_list_model)

        self.class_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.class_list.customContextMenuRequested.connect(
            self.show_class_list_menu)
        self.class_list.doubleClicked.connect(self.class_list_double_click)

        self.current_class_search = ''

        bar = QScrollBar()
        bar.setFixedWidth(0)
        bar.setFixedHeight(0)
        self.trace_list.setHorizontalScrollBar(bar)
        bar = QScrollBar()
        bar.setFixedWidth(0)
        bar.setFixedHeight(0)
        self.class_list.setHorizontalScrollBar(bar)

        self.setup_splitter.addWidget(self.trace_list)
        self.setup_splitter.addWidget(self.class_list)

        layout.addWidget(self.setup_splitter)
        layout.addWidget(self.events_list)

        self.setLayout(layout)

    def class_list_double_click(self, item):
        item = self.class_list_model.itemFromIndex(item)
        try:
            if self.trace_classes.index(item.text()) >= 0:
                return
        except:
            pass
        self.trace_classes.append(item.text())
        self.trace_list_model.appendRow([QStandardItem(item.text())])
        self.trace_list_model.sort(0, Qt.AscendingOrder)

    def on_enumeration_start(self):
        self.class_list_model.setRowCount(0)

    def on_enumeration_match(self, java_class):
        try:
            if PREFIXED_CLASS.index(java_class) >= 0:
                try:
                    if self.trace_classes.index(java_class) >= 0:
                        return
                except:
                    pass
                self.trace_list_model.appendRow(QStandardItem(java_class))
                self.trace_classes.append(java_class)
        except:
            pass

        self.class_list_model.appendRow([QStandardItem(java_class)])

    def on_enumeration_complete(self):
        self.class_list_model.sort(0, Qt.AscendingOrder)
        self.trace_list_model.sort(0, Qt.AscendingOrder)

    def on_event(self, data):
        trace, event, clazz, data = data
        if trace == 'java_trace':
            self.events_list.add_event({
                'event': event,
                'class': clazz,
                'data': data.replace(',', ', ')
            })
            self._entries_lbl.setText('Events: %d' %
                                      len(self.events_list.data))

    def pause_trace(self):
        self.app.dwarf.dwarf_api('stopJavaTracer')
        self.tracing = False

    def show_class_list_menu(self, pos):
        menu = QMenu()
        search = menu.addAction('Search')
        action = menu.exec_(self.class_list.mapToGlobal(pos))
        if action:
            if action == search:
                self.class_list._on_cm_search()

    def start_trace(self):
        self.app.dwarf.dwarf_api('startJavaTracer', [self.trace_classes])
        self.trace_depth = 0
        self.tracing = True
        self.setup_splitter.setVisible(False)
        self.events_list.setVisible(True)

    def stop_trace(self):
        self.app.dwarf.dwarf_api('stopJavaTracer')
        self.tracing = False
        self.setup_splitter.setVisible(True)
        self.events_list.setVisible(False)
        self.events_list.clear()

    def trace_list_double_click(self, model_index):
        row = self.trace_list_model.itemFromIndex(model_index).row()
        if row != -1:
            trace_entry = self.trace_list_model.item(row, 0).text()

            if not trace_entry:
                return

            try:
                index = self.trace_classes.index(trace_entry)
                self.trace_classes.pop(index)
                self.trace_list_model.removeRow(row)
            except ValueError:
                pass

    def keyPressEvent(self, event):
        if event.modifiers() & Qt.ControlModifier:
            if event.key() == Qt.Key_F:
                self.search()
        super(JavaTracePanel, self).keyPressEvent(event)
Example #29
0
class MainWindow(Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.filename = ":memory:"
        self.engine = create_engine('sqlite:///' + self.filename)
        Base.metadata.create_all(self.engine)
        self.Session = sessionmaker(bind=self.engine)
        self.session = self.Session()

    def setupUi(self, main_window):
        super().setupUi(main_window)

        #Toolbar
        self.actionAdd_student.triggered.connect(self.addStudent)
        self.actionAdd_subject.triggered.connect(self.addSubject)

        #File menu actions
        self.actionOpen.triggered.connect(self.openFile)
        self.actionSave.triggered.connect(self.saveFile)
        self.actionSave_as.triggered.connect(self.saveFileAs)
        self.actionQuit.triggered.connect(QApplication.instance().quit)

        #Help menu actions
        self.actionAbout.triggered.connect(self.showAbout)

        #Students dock
        self.studentLineEdit.textChanged['QString'].connect(
            self.searchStudents)

        self.student_model = QStandardItemModel()
        self.studentList.setModel(self.student_model)
        self.studentList.doubleClicked['QModelIndex'].connect(self.editStudent)
        self.studentList.selectionModel().selectionChanged[
            'QItemSelection',
            'QItemSelection'].connect(self.changeSelectedStudent)
        self.studentList.setSelectionMode(
            QAbstractItemView.SelectionMode.SingleSelection)
        self.updateStudentModel()

        #Subjects dock
        self.subjectLineEdit.textChanged['QString'].connect(
            self.searchSubjects)

        self.subject_model = QStandardItemModel()
        self.subjectList.setModel(self.subject_model)
        self.subjectList.doubleClicked['QModelIndex'].connect(self.editSubject)
        self.updateSubjectModel()

        #Main view
        self.schedule_model = QStandardItemModel(12, 7)
        self.scheduleView.setModel(self.schedule_model)
        self.scheduleView.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.scheduleView.verticalHeader().setSectionResizeMode(
            QHeaderView.Stretch)
        self.updateScheduleModel()

    def searchStudents(self, name):
        self.student_model.clear()
        for i in self.session.query(Student).filter(
                Student.name.ilike('%' + name + '%')):
            it = QStandardItem()
            it.setText(i.name)
            it.setData(i)
            it.setEditable(False)
            self.student_model.appendRow(it)

    def searchSubjects(self, name):
        self.subject_model.clear()
        for i in self.session.query(Subject).filter(
                Subject.name.ilike('%' + name + '%')):
            it = QStandardItem()
            it.setText(i.name)
            it.setData(i)
            it.setEditable(False)
            self.subject_model.appendRow(it)

    def updateStudentModel(self):
        self.searchStudents(self.studentLineEdit.text())

    def updateSubjectModel(self):
        self.searchSubjects(self.subjectLineEdit.text())

    def updateScheduleModel(self):
        self.schedule_model.clear()
        self.schedule_model.setHorizontalHeaderLabels([
            "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday",
            "Sunday"
        ])
        for i in DayOfWeek:
            for j in range(12):
                q = self.session.query(TimePlace).filter(
                    TimePlace.day == i, TimePlace.slot == j)
                subjects = []
                for tp in q:
                    subjects.append(' '.join(
                        [tp.subject.abbreviation, tp.place.name]))
                it = QStandardItem()
                it.setText('\n'.join(subjects))
                it.setEditable(False)
                self.schedule_model.setItem(j, int(i), it)

    def addStudent(self):
        dialog = QDialog()
        content = EditStudent(self.session)
        content.setupUi(dialog)
        dialog.exec_()
        self.updateStudentModel()
        self.updateScheduleModel()

    def addSubject(self):
        dialog = QDialog()
        content = EditSubject(self.session)
        content.setupUi(dialog)
        dialog.exec_()
        self.updateSubjectModel()
        self.updateScheduleModel()

    def editStudent(self, idx):
        dialog = QDialog()
        content = EditStudent(self.session,
                              self.student_model.itemFromIndex(idx).data())
        content.setupUi(dialog)
        dialog.exec_()
        self.updateStudentModel()
        self.updateScheduleModel()

    def editSubject(self, idx):
        dialog = QDialog()
        content = EditSubject(self.session,
                              self.subject_model.itemFromIndex(idx).data())
        content.setupUi(dialog)
        dialog.exec_()
        self.updateSubjectModel()
        self.updateScheduleModel()

    def changeSelectedStudent(self, selected, unselected):
        tps = []
        for s in selected:
            for i in s.indexes():
                student = self.student_model.itemFromIndex(i).data()
                for subject in student.subjects:
                    for tp in subject.time_places:
                        tps.append((tp.slot, int(tp.day)))

        crossed = QBrush(Qt.black, Qt.BDiagPattern)
        normal = QBrush(Qt.white)

        for i in range(12):
            for j in map(int, DayOfWeek):
                if (i, j) in tps:
                    self.schedule_model.item(i, j).setBackground(normal)
                else:
                    self.schedule_model.item(i, j).setBackground(crossed)

    def openFile(self):
        new_filename = QFileDialog.getOpenFileName(
            caption="Open file", filter="SQLite3 file (*.sqlite3)")[0]
        if new_filename:
            self.filename = new_filename
            self.engine.dispose()
            self.engine = create_engine('sqlite:///' + self.filename)
            Base.metadata.create_all(self.engine)
            self.Session = sessionmaker(bind=self.engine)
            self.session = self.Session()
            self.updateStudentModel()
            self.updateSubjectModel()
            self.updateScheduleModel()

    def saveFile(self):
        if self.filename == ':memory:':
            self.saveFileAs()
        else:
            self.session.commit()

    def saveFileAs(self):
        new_filename = QFileDialog.getSaveFileName(
            caption="Save file as", filter="SQLite3 file (*.sqlite3)")[0]
        if new_filename:
            new_engine = create_engine('sqlite:///' + new_filename)
            Base.metadata.drop_all(new_engine)
            Base.metadata.create_all(new_engine)
            tables = Base.metadata.tables
            for tbl in tables:
                data = self.engine.execute(tables[tbl].select()).fetchall()
                if data:
                    new_engine.execute(tables[tbl].insert(), data)
            self.engine.dispose()
            self.engine = new_engine
            self.Session = sessionmaker(bind=self.engine)
            self.session = self.Session()
            self.filename = new_filename
            self.updateSubjectModel()
            self.updateStudentModel()
            self.updateScheduleModel()

    def showAbout(self):
        dialog = QDialog()
        ui = About()
        ui.setupUi(dialog)
        dialog.exec_()
Example #30
0
class Explorer(QWidget):
    """
    This class implements the diagram predicate node explorer.
    """
    def __init__(self, mainwindow):
        """
        Initialize the Explorer.
        :type mainwindow: MainWindow
        """
        super().__init__(mainwindow)
        self.expanded = {}
        self.searched = {}
        self.scrolled = {}
        self.mainview = None
        self.iconA = QIcon(':/icons/treeview-icon-attribute')
        self.iconC = QIcon(':/icons/treeview-icon-concept')
        self.iconD = QIcon(':/icons/treeview-icon-datarange')
        self.iconI = QIcon(':/icons/treeview-icon-instance')
        self.iconR = QIcon(':/icons/treeview-icon-role')
        self.iconV = QIcon(':/icons/treeview-icon-value')
        self.search = StringField(self)
        self.search.setAcceptDrops(False)
        self.search.setClearButtonEnabled(True)
        self.search.setPlaceholderText('Search...')
        self.search.setFixedHeight(30)
        self.model = QStandardItemModel(self)
        self.proxy = QSortFilterProxyModel(self)
        self.proxy.setDynamicSortFilter(False)
        self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.proxy.setSortCaseSensitivity(Qt.CaseSensitive)
        self.proxy.setSourceModel(self.model)
        self.view = ExplorerView(mainwindow, self)
        self.view.setModel(self.proxy)
        self.mainLayout = QVBoxLayout(self)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.addWidget(self.search)
        self.mainLayout.addWidget(self.view)
        self.setContentsMargins(0, 0, 0, 0)
        self.setMinimumWidth(216)
        self.setMinimumHeight(160)

        connect(self.view.doubleClicked, self.itemDoubleClicked)
        connect(self.view.pressed, self.itemPressed)
        connect(self.view.collapsed, self.itemCollapsed)
        connect(self.view.expanded, self.itemExpanded)
        connect(self.search.textChanged, self.filterItem)

    ####################################################################################################################
    #                                                                                                                  #
    #   EVENTS                                                                                                         #
    #                                                                                                                  #
    ####################################################################################################################

    def paintEvent(self, paintEvent):
        """
        This is needed for the widget to pick the stylesheet.
        :type paintEvent: QPaintEvent
        """
        option = QStyleOption()
        option.initFrom(self)
        painter = QPainter(self)
        style = self.style()
        style.drawPrimitive(QStyle.PE_Widget, option, painter, self)

    ####################################################################################################################
    #                                                                                                                  #
    #   SLOTS                                                                                                          #
    #                                                                                                                  #
    ####################################################################################################################

    @pyqtSlot('QGraphicsItem')
    def add(self, item):
        """
        Add a node in the tree view.
        :type item: AbstractItem
        """
        if item.node and item.predicate:
            parent = self.parentFor(item)
            if not parent:
                parent = ParentItem(item)
                parent.setIcon(self.iconFor(item))
                self.model.appendRow(parent)
                self.proxy.sort(0, Qt.AscendingOrder)
            child = ChildItem(item)
            child.setData(item)
            parent.appendRow(child)
            self.proxy.sort(0, Qt.AscendingOrder)

    @pyqtSlot(str)
    def filterItem(self, key):
        """
        Executed when the search box is filled with data.
        :type key: str
        """
        if self.mainview:
            self.proxy.setFilterFixedString(key)
            self.proxy.sort(Qt.AscendingOrder)
            self.searched[self.mainview] = key

    @pyqtSlot('QModelIndex')
    def itemCollapsed(self, index):
        """
        Executed when an item in the tree view is collapsed.
        :type index: QModelIndex
        """
        if self.mainview:
            if self.mainview in self.expanded:
                item = self.model.itemFromIndex(self.proxy.mapToSource(index))
                expanded = self.expanded[self.mainview]
                expanded.remove(item.text())

    @pyqtSlot('QModelIndex')
    def itemDoubleClicked(self, index):
        """
        Executed when an item in the tree view is double clicked.
        :type index: QModelIndex
        """
        item = self.model.itemFromIndex(self.proxy.mapToSource(index))
        node = item.data()
        if node:
            self.selectNode(node)
            self.focusNode(node)

    @pyqtSlot('QModelIndex')
    def itemExpanded(self, index):
        """
        Executed when an item in the tree view is expanded.
        :type index: QModelIndex
        """
        if self.mainview:
            item = self.model.itemFromIndex(self.proxy.mapToSource(index))
            if self.mainview not in self.expanded:
                self.expanded[self.mainview] = set()
            expanded = self.expanded[self.mainview]
            expanded.add(item.text())

    @pyqtSlot('QModelIndex')
    def itemPressed(self, index):
        """
        Executed when an item in the tree view is clicked.
        :type index: QModelIndex
        """
        item = self.model.itemFromIndex(self.proxy.mapToSource(index))
        node = item.data()
        if node:
            self.selectNode(node)

    @pyqtSlot('QGraphicsItem')
    def remove(self, item):
        """
        Remove a node from the tree view.
        :type item: AbstractItem
        """
        if item.node and item.predicate:
            parent = self.parentFor(item)
            if parent:
                child = self.childFor(parent, item)
                if child:
                    parent.removeRow(child.index().row())
                if not parent.rowCount():
                    self.model.removeRow(parent.index().row())

    ####################################################################################################################
    #                                                                                                                  #
    #   AUXILIARY METHODS                                                                                              #
    #                                                                                                                  #
    ####################################################################################################################

    @staticmethod
    def childFor(parent, node):
        """
        Search the item representing this node among parent children.
        :type parent: QStandardItem
        :type node: AbstractNode
        """
        key = ChildItem.key(node)
        for i in range(parent.rowCount()):
            child = parent.child(i)
            if child.text() == key:
                return child
        return None

    def parentFor(self, node):
        """
        Search the parent element of the given node.
        :type node: AbstractNode
        :rtype: QStandardItem
        """
        key = ParentItem.key(node)
        for i in self.model.findItems(key, Qt.MatchExactly):
            n = i.child(0).data()
            if node.item is n.item:
                return i
        return None

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def browse(self, view):
        """
        Set the widget to inspect the given view.
        :type view: MainView
        """
        self.reset()
        self.mainview = view

        if self.mainview:

            scene = self.mainview.scene()
            connect(scene.index.sgnItemAdded, self.add)
            connect(scene.index.sgnItemRemoved, self.remove)

            for item in scene.index.nodes():
                self.add(item)

            if self.mainview in self.expanded:
                expanded = self.expanded[self.mainview]
                for i in range(self.model.rowCount()):
                    item = self.model.item(i)
                    index = self.proxy.mapFromSource(
                        self.model.indexFromItem(item))
                    self.view.setExpanded(index, item.text() in expanded)

            key = ''
            if self.mainview in self.searched:
                key = self.searched[self.mainview]
            self.search.setText(key)

            if self.mainview in self.scrolled:
                rect = self.rect()
                item = first(self.model.findItems(
                    self.scrolled[self.mainview]))
                for i in range(self.model.rowCount()):
                    self.view.scrollTo(
                        self.proxy.mapFromSource(
                            self.model.indexFromItem(self.model.item(i))))
                    index = self.proxy.mapToSource(
                        self.view.indexAt(rect.topLeft()))
                    if self.model.itemFromIndex(index) is item:
                        break

    def reset(self):
        """
        Clear the widget from inspecting the current view.
        """
        if self.mainview:

            rect = self.rect()
            item = self.model.itemFromIndex(
                self.proxy.mapToSource(self.view.indexAt(rect.topLeft())))
            if item:
                node = item.data()
                key = ParentItem.key(node) if node else item.text()
                self.scrolled[self.mainview] = key
            else:
                self.scrolled.pop(self.mainview, None)

            try:
                scene = self.mainview.scene()
                disconnect(scene.index.sgnItemAdded, self.add)
                disconnect(scene.index.sgnItemRemoved, self.remove)
            except RuntimeError:
                pass
            finally:
                self.mainview = None

        self.model.clear()

    def flush(self, view):
        """
        Flush the cache of the given mainview.
        :type view: MainView
        """
        self.expanded.pop(view, None)
        self.searched.pop(view, None)
        self.scrolled.pop(view, None)

    def iconFor(self, node):
        """
        Returns the icon for the given node.
        :type node:
        """
        if node.item is Item.AttributeNode:
            return self.iconA
        if node.item is Item.ConceptNode:
            return self.iconC
        if node.item is Item.ValueDomainNode:
            return self.iconD
        if node.item is Item.ValueRestrictionNode:
            return self.iconD
        if node.item is Item.IndividualNode:
            if node.identity is Identity.Instance:
                return self.iconI
            if node.identity is Identity.Value:
                return self.iconV
        if node.item is Item.RoleNode:
            return self.iconR

    def focusNode(self, node):
        """
        Focus the given node in the main view.
        :type node: AbstractNode
        """
        if self.mainview:
            self.mainview.centerOn(node)

    def selectNode(self, node):
        """
        Select the given node in the main view.
        :type node: AbstractNode
        """
        if self.mainview:
            scene = self.mainview.scene()
            scene.clearSelection()
            node.setSelected(True)
Example #31
0
class Tags():
    """class for working with tags model:
        - adding tags
        - deletening tags
        - loading tags DB
        - saving tags DB
    """
    
    def __init__( self, myController ):
        self.controller = myController
        self.wereAnyChanges = False
        self.tagDict = {}
        self.tagModel = QStandardItemModel( 0,1 )
        self.tagModel.setColumnCount( 2 )
        self.tagModel.setHeaderData( 0, Qt.Horizontal, QVariant( "TagList for Library:" ) )
        self.tagModel.setHorizontalHeaderLabels( [ "Tags", "Quantity of tags" ] )
        #self.tagModel.itemChanged.connect( self.HandleTagChanged )
        self.tagNameToTagItemsDict = {}
        self.InitDefaultTags()
        if os.path.exists( "myTags.xml" ) == False:
            self.controller.PrintToLog( "No Tag library found, created new one" )
        else:
            self.LoadTagsFromXML()
    
    def InitDefaultTags( self ):
        pass
        #self.AddTag( "IMPORTANT" )

    def AddTag( self, name ):
        newTag = QStandardItem( name )
        newTagQuantity = QItemToSortByNum( "0" )
        newTag.setFlags( Qt.ItemIsUserCheckable | Qt.ItemIsEnabled | Qt.ItemIsSelectable )
        newTag.setData( QVariant( Qt.Unchecked ), Qt.CheckStateRole )
        self.tagDict[ name ] = newTag
        currentRow = [ newTag, newTagQuantity ]
        self.tagNameToTagItemsDict[ name ] = currentRow
        self.tagModel.appendRow( currentRow )
        self.tagModel.sort( Qt.AscendingOrder )
        self.wereAnyChanges = True

    def LoadTagsFromXML( self ):
        xmlParser = QXmlSimpleReader()
        xmlContentHandler = myXmlContentHandler( self )
        xmlFile = QFile( "myTags.xml" )
        xmlInputSource = QXmlInputSource( xmlFile )    
        xmlParser.setContentHandler( xmlContentHandler )  
      
        if( xmlParser.parse( xmlInputSource ) ):
            self.controller.PrintToLog( "Tag-library successfully loaded!" )
            self.tagModel.sort( 1, Qt.AscendingOrder )
        else:  
            self.controller.PrintToLog( "Parsing of Tag-library Failed!" )

    def SaveTagModel( self ):
        if self.wereAnyChanges == False:
            return
        QFile.remove( "myTags_bcp.xml" )
        xmlOldFile = QFile( "myTags.xml" )        
        xmlOldFile.rename( "myTags_bcp.xml" )

        xmlWriter = QXmlStreamWriter()
        xmlFile = QFile( "myTags.xml" )
        
        if ( xmlFile.open( QIODevice.WriteOnly ) == False ):    
            QMessageBox.warning( 0, "Error!", "Error opening file" )  
            QFile.remove( "myTags.xml" )
            xmlOldFile.rename( "myTags.xml" )  
        else :    
            xmlWriter.setDevice( xmlFile )	
            xmlWriter.writeStartDocument()
            xmlWriter.writeStartElement( "TagLibrary" )
            
            klist = list( self.tagDict.keys() )
            klist.sort() 
            for tagName in klist:
                xmlWriter.writeStartElement( tagName )
                xmlWriter.writeEndElement() 
                xmlWriter.autoFormattingIndent()
            xmlWriter.writeEndElement()
            xmlWriter.writeEndDocument()
        self.controller.PrintToLog( "TagLibrary successfully saved!" )
        #if everything was written successfully, replace a tag file with new
        #one
        #QFile.remove( "myTags.xml" )
        #xmlFile.rename( "myTags.xml" )

    def DelTag( self, selectedIndexes ):
        for index in selectedIndexes:
            selectedItem = self.tagModel.itemFromIndex( index )
            del self.tagDict[ selectedItem.text() ]
            self.tagModel.removeRow( selectedItem.row() )
Example #32
0
class EmulatorPanel(QWidget):
    def __init__(self, app, *__args):
        super().__init__(*__args)

        self.app = app
        self.emulator = self.app.dwarf.emulator
        self.until_address = 0

        self._uc_user_arch = None
        self._uc_user_mode = None
        self._cs_user_arch = None
        self._cs_user_mode = None

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        self._toolbar_container = QHBoxLayout()
        self._toolbar = QToolBar()
        self._toolbar.addAction('Start', self.handle_start)
        self._toolbar.addAction('Step', self.handle_step)
        self._toolbar.addAction('Step next call', self.handle_step_next_call)
        self._toolbar.addAction('Step next jump', self.handle_step_next_jump)
        self._toolbar.addAction('Stop', self.handle_stop)
        self._toolbar.addAction('Clear', self.handle_clear)
        self._toolbar.addAction('Options', self.handle_options)
        self._toolbar_container.addWidget(self._toolbar)

        selection_layout = QHBoxLayout()
        selection_layout.setAlignment(Qt.AlignRight)
        self.cpu_selection = QComboBox(self)
        for v in unicorn_const.__dict__:
            if 'UC_ARCH_' in v:
                self.cpu_selection.addItem('_'.join(v.split('_')[2:]).lower(),
                                           unicorn_const.__dict__[v])
        self.cpu_selection.activated[str].connect(self._on_cpu_selection)
        self.mode_selection = QComboBox(self)
        for v in unicorn_const.__dict__:
            if 'UC_MODE_' in v:
                self.mode_selection.addItem('_'.join(v.split('_')[2:]).lower(),
                                            unicorn_const.__dict__[v])
        self.mode_selection.activated[str].connect(self._on_mode_selection)
        selection_layout.addWidget(self.cpu_selection)
        selection_layout.addWidget(self.mode_selection)
        self._toolbar_container.addLayout(selection_layout)

        layout.addLayout(self._toolbar_container)

        self.tabs = QTabWidget()
        self.assembly = DisassemblyView(self.app)
        self.assembly.display_jumps = False
        self.assembly.follow_jumps = False
        self.memory_table = MemoryPanel(self.app)
        self.memory_table._read_only = True
        self.tabs.addTab(self.assembly, 'Code')
        self.tabs.addTab(self.memory_table, 'Memory')

        layout.addWidget(self.tabs)

        self.ranges_list = DwarfListView(self.app)
        self.ranges_list.doubleClicked.connect(self.ranges_item_double_clicked)
        self._ranges_model = QStandardItemModel(0, 2)
        self._ranges_model.setHeaderData(0, Qt.Horizontal, 'Memory')
        self._ranges_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(1, Qt.Horizontal, 'Size')
        self._ranges_model.setHeaderData(1, Qt.Horizontal, Qt.AlignLeft,
                                         Qt.TextAlignmentRole)
        self.ranges_list.setModel(self._ranges_model)
        self.tabs.addTab(self.ranges_list, 'Ranges')

        self._access_list = DwarfListView(self.app)
        self._access_list.doubleClicked.connect(
            self.access_item_double_clicked)
        self._access_model = QStandardItemModel(0, 3)
        self._access_model.setHeaderData(0, Qt.Horizontal, 'Address')
        self._access_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._access_model.setHeaderData(1, Qt.Horizontal, 'Access')
        self._access_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._access_model.setHeaderData(2, Qt.Horizontal, 'Value')
        self._access_list.setModel(self._access_model)
        self.tabs.addTab(self._access_list, 'Access')

        layout.setSpacing(0)
        self.setLayout(layout)

        self.console = self.app.console.get_emu_console()

        self.emulator.onEmulatorSetup.connect(self.on_emulator_setup)
        self.emulator.onEmulatorStart.connect(self.on_emulator_start)
        self.emulator.onEmulatorStop.connect(self.on_emulator_stop)
        # self.emulator.onEmulatorStep.connect(self.on_emulator_step)
        self.emulator.onEmulatorHook.connect(self.on_emulator_hook)
        self.emulator.onEmulatorMemoryHook.connect(
            self.on_emulator_memory_hook)
        self.emulator.onEmulatorMemoryRangeMapped.connect(
            self.on_emulator_memory_range_mapped)
        self.emulator.onEmulatorLog.connect(self.on_emulator_log)

        self._require_register_result = None
        self._last_instruction_address = 0

    def _on_cpu_selection(self, cpu):
        self._uc_user_arch = unicorn_const.__dict__['UC_ARCH_' + cpu.upper()]
        self._cs_user_arch = capstone.__dict__['CS_ARCH_' + cpu.upper()]
        self._uc_user_mode = unicorn_const.__dict__[
            'UC_MODE_' + self.mode_selection.itemText(
                self.mode_selection.currentIndex()).upper()]
        self._cs_user_mode = capstone.__dict__[
            'CS_MODE_' + self.mode_selection.itemText(
                self.mode_selection.currentIndex()).upper()]

    def _on_mode_selection(self, mode):
        self._uc_user_mode = unicorn_const.__dict__['UC_MODE_' + mode.upper()]
        self._cs_user_mode = capstone.__dict__['CS_MODE_' + mode.upper()]
        self._uc_user_arch = unicorn_const.__dict__[
            'UC_ARCH_' + self.cpu_selection.itemText(
                self.cpu_selection.currentIndex()).upper()]
        self._cs_user_arch = capstone.__dict__[
            'CS_ARCH_' + self.cpu_selection.itemText(
                self.cpu_selection.currentIndex()).upper()]

    def resizeEvent(self, event):
        self.ranges_list.setFixedHeight((self.height() / 100) * 25)
        self.ranges_list.setFixedWidth((self.width() / 100) * 30)
        self._access_list.setFixedHeight((self.height() / 100) * 25)
        return super().resizeEvent(event)

    def handle_clear(self):
        self.ranges_list.clear()
        self._access_list.clear()
        self.assembly._lines.clear()
        self.assembly.viewport().update()
        # self.memory_table.setRowCount(0)
        self.console.clear()
        self.emulator.clean()

    def handle_options(self):
        EmulatorConfigsDialog.show_dialog(self.app.dwarf)

    def handle_start(self):
        ph = ''
        if self.until_address > 0:
            ph = hex(self.until_address)
        address, inp = InputDialog.input_pointer(
            self.app, input_content=ph, hint='pointer to last instruction')
        if address > 0:
            self.until_address = address
            self.app.console_panel.show_console_tab('emulator')
            self.emulator.emulate(self.until_address,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
            # if err > 0:
            #    self.until_address = 0
            #    self.console.log('cannot start emulator. err: %d' % err)
            #    return

    def handle_step(self):
        self.app.console_panel.show_console_tab('emulator')

        try:
            self.emulator.emulate(step_mode=STEP_MODE_SINGLE,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
        except self.emulator.EmulatorAlreadyRunningError:
            self.console.log('Emulator already running')
        except self.emulator.EmulatorSetupFailedError as error:
            self.until_address = 0
            self.console.log(error)

    def handle_step_next_call(self):
        self.app.console_panel.show_console_tab('emulator')

        try:
            self.emulator.emulate(step_mode=STEP_MODE_FUNCTION,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
        except self.emulator.EmulatorAlreadyRunningError:
            self.console.log('Emulator already running')
        except self.emulator.EmulatorSetupFailedError as error:
            self.until_address = 0
            self.console.log(error)

    def handle_step_next_jump(self):
        self.app.console_panel.show_console_tab('emulator')

        try:
            self.emulator.emulate(step_mode=STEP_MODE_JUMP,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
        except self.emulator.EmulatorAlreadyRunningError:
            self.console.log('Emulator already running')
        except self.emulator.EmulatorSetupFailedError as error:
            self.until_address = 0
            self.console.log(error)

    def handle_stop(self):
        self.emulator.stop()

    def on_emulator_hook(self, instruction):
        # @PinkiePonkie why setting context here (which is triggered each instruction) and later, set it again
        # in emulator_stop? step = double hit in set_context, running emulation on more than 1 instruction
        # doesn't need spam of set_context
        # self.app.context_panel.set_context(0, 2, self.emulator.current_context)

        # check if the previous hook is waiting for a register result
        if self._require_register_result is not None:
            row = 1
            if len(self._require_register_result) == 1:
                res = 'jump = %s' % (hex(self._require_register_result[0]))
            else:
                res = '%s = %s' % (self._require_register_result[1],
                                   hex(
                                       self.emulator.uc.reg_read(
                                           self._require_register_result[0])))
            if len(self.assembly._lines) > 1:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row] is None:
                    row = 2

                telescope = self.get_telescope(
                    self.emulator.uc.reg_read(
                        self._require_register_result[0]))
                if telescope is not None and telescope != 'None':
                    res += ' (' + telescope + ')'

                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res
                # invalidate
                self._require_register_result = None

        # check if the code jumped
        self._last_instruction_address = instruction.address

        self.assembly.add_instruction(instruction)

        # add empty line if jump
        if instruction.is_jump or instruction.is_call:
            self.assembly.add_instruction(None)
            self._require_register_result = [instruction.jump_address]
        else:
            # implicit regs read are notified later through mem access
            if len(instruction.regs_read) == 0:
                if len(instruction.operands) > 0:
                    for i in instruction.operands:
                        if i.type == CS_OP_REG:
                            self._require_register_result = [
                                i.value.reg,
                                instruction.reg_name(i.value.reg)
                            ]
                            break
        self.assembly.verticalScrollBar().setValue(len(self.assembly._lines))
        self.assembly.viewport().update()

        if instruction.is_call:
            range_ = Range(Range.SOURCE_TARGET, self.app.dwarf)
            if range_.init_with_address(instruction.address,
                                        require_data=False) > 0:
                if range_.base > instruction.call_address > range_.tail:
                    if self.emulator.step_mode == STEP_MODE_NONE:
                        self.emulator.stop()
                    action = JumpOutsideTheBoxDialog.show_dialog(
                        self.app.dwarf)
                    if action == 0:
                        # step to jump
                        if self.emulator.step_mode != STEP_MODE_NONE:
                            self.handle_step()
                        else:
                            self.emulator.emulate(self.until_address,
                                                  user_arch=self._uc_user_arch,
                                                  user_mode=self._uc_user_mode,
                                                  cs_arch=self._cs_user_arch,
                                                  cs_mode=self._cs_user_mode)
                    if action == 1:
                        # step to next jump
                        if self.emulator.step_mode != STEP_MODE_NONE:
                            self.handle_step_next_jump()
                        else:
                            self.emulator.emulate(self.until_address,
                                                  user_arch=self._uc_user_arch,
                                                  user_mode=self._uc_user_mode,
                                                  cs_arch=self._cs_user_arch,
                                                  cs_mode=self._cs_user_mode)
                    elif action == 2:
                        # hook lr
                        hook_addr = instruction.address + instruction.size
                        if instruction.thumb:
                            hook_addr += 1
                        self.app.dwarf.hook_native(input_=hex(hook_addr))

    def on_emulator_log(self, log):
        self.app.console_panel.show_console_tab('emulator')
        self.console.log(log)

    def on_emulator_memory_hook(self, data):
        uc, access, address, value = data
        _address = QStandardItem()
        if self.ranges_list.uppercase_hex:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016X}'.format(address)
            else:
                str_frmt = '0x{0:08X}'.format(address)
        else:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016x}'.format(address)
            else:
                str_frmt = '0x{0:08x}'.format(address)
        _address.setText(str_frmt)
        _address.setTextAlignment(Qt.AlignCenter)

        _access = QStandardItem()
        if access == UC_MEM_READ:
            _access.setText('READ')
        elif access == UC_MEM_WRITE:
            _access.setText('WRITE')
        elif access == UC_MEM_FETCH:
            _access.setText('FETCH')
        _access.setTextAlignment(Qt.AlignCenter)

        _value = QStandardItem()
        _value.setText(str(value))

        self._access_model.appendRow([_address, _access, _value])

        res = None
        row = 1
        if len(self.assembly._lines) > 1:
            if self.assembly._lines[len(self.assembly._lines) - row] is None:
                row = 2
            if access == UC_MEM_READ:
                if self._require_register_result is not None:
                    if len(self._require_register_result) > 1:
                        res = '%s = %s' % (self._require_register_result[1],
                                           hex(value))
            else:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row].string:
                    res = '%s, %s = %s' % (self.assembly._lines[
                        len(self.assembly._lines) - row].string, hex(address),
                                           hex(value))
                else:
                    res = '%s = %s' % (hex(address), hex(value))
            if res is not None:
                telescope = self.get_telescope(value)
                if telescope is not None and telescope != 'None':
                    res += ' (' + telescope + ')'
                # invalidate
                self._require_register_result = None
                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res

    def get_telescope(self, address):
        try:
            size = self.app.dwarf.pointer_size
            telescope = self.emulator.uc.mem_read(address, size)
            try:
                for i in range(len(telescope)):
                    if int(telescope[i]) == 0x0 and i != 0:
                        st = telescope.decode('utf8')
                        return st
                st = telescope.decode('utf8')
                if len(st) != size:
                    return '0x%s' % telescope.hex()
                while True:
                    telescope = self.emulator.uc.mem_read(address + size, 1)
                    if int(telescope) == 0x0:
                        break
                    st += telescope.decode('utf8')
                    size += 1
                return st
            except:
                return '0x%s' % telescope.hex()
        except UcError as e:
            # read from js
            telescope = self.app.dwarf.dwarf_api('getAddressTs', address)
            if telescope is None:
                return None
            telescope = str(telescope[1]).replace('\n', ' ')
            if len(telescope) > 50:
                telescope = telescope[:50] + '...'
            return telescope

    def on_emulator_memory_range_mapped(self, data):
        address, size = data
        _address = QStandardItem()
        if self.ranges_list.uppercase_hex:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016X}'.format(address)
            else:
                str_frmt = '0x{0:08X}'.format(address)
        else:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016x}'.format(address)
            else:
                str_frmt = '0x{0:08x}'.format(address)
        _address.setText(str_frmt)
        _address.setTextAlignment(Qt.AlignCenter)
        _size = QStandardItem()
        _size.setText("{0:,d}".format(int(size)))
        self._ranges_model.appendRow([_address, _size])

    def on_emulator_setup(self, data):
        user_arch = data[0]
        user_mode = data[1]
        if user_arch is not None and user_mode is not None:
            index = self.cpu_selection.findData(user_arch)
            self.cpu_selection.setCurrentIndex(index)
            index = self.mode_selection.findData(user_mode)
            self.mode_selection.setCurrentIndex(index)

    def on_emulator_start(self):
        pass

    def on_emulator_stop(self):
        self.app.context_panel.set_context(0, 2, self.emulator.current_context)
        # check if the previous hook is waiting for a register result
        if self._require_register_result is not None:
            row = 1
            if len(self._require_register_result) == 1:
                res = 'jump = %s' % (hex(self._require_register_result[0]))
            else:
                res = '%s = %s' % (self._require_register_result[1],
                                   hex(
                                       self.emulator.uc.reg_read(
                                           self._require_register_result[0])))
                telescope = self.get_telescope(
                    self.emulator.uc.reg_read(
                        self._require_register_result[0]))
                if telescope is not None and telescope != 'None':
                    res += ' (' + telescope + ')'

            if len(self.assembly._lines) > 1:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row] is None:
                    row = 2
                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res
                # invalidate
                self._require_register_result = None

    def ranges_item_double_clicked(self, model_index):
        row = self._ranges_model.itemFromIndex(model_index).row()
        if row != -1:
            item = self._ranges_model.item(row, 0).text()
            self.memory_table.read_memory(item)
            self.tabs.setCurrentIndex(1)

    def access_item_double_clicked(self, model_index):
        row = self._access_model.itemFromIndex(model_index).row()
        if row != -1:
            item = self._access_model.item(row, 0).text()
            self.memory_table.read_memory(item)
            self.tabs.setCurrentIndex(1)
Example #33
0
class ImageView(QListView):
    def __init__(self, *args, **kwargs):
        super(ImageView, self).__init__(*args, **kwargs)
        self.setFrameShape(self.NoFrame)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setEditTriggers(self.NoEditTriggers)
        self.setDropIndicatorShown(True)
        self.setDragDropMode(self.DragDrop)
        self.setDefaultDropAction(Qt.IgnoreAction)
        self.setSelectionMode(self.ExtendedSelection)
        self.setVerticalScrollMode(self.ScrollPerPixel)
        self.setHorizontalScrollMode(self.ScrollPerPixel)
        self.setFlow(self.LeftToRight)
        self.setWrapping(True)
        self.setResizeMode(self.Adjust)
        self.setSpacing(6)
        self.setViewMode(self.IconMode)
        self.setWordWrap(True)
        self.setSelectionRectVisible(True)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        # 解决拖动到顶部或者底部自动滚动
        self.setAutoScrollMargin(150)
        self.verticalScrollBar().setSingleStep(ScrollPixel)
        # 设置model
        self.dmodel = QStandardItemModel(self)
        self.setModel(self.dmodel)

        # 大图控件
        self.bigView = BigImageView(background='#323232')

    def addItem(self, image):
        if isinstance(image, str):
            image = QPixmap(image)
        # 添加一个item
        item = QStandardItem()
        # 记录原始图片
        item.setData(image, Qt.UserRole + 1)  # 用于双击的时候取出来
        # 缩放成小图并显示
        item.setData(
            image.scaled(60, 60, Qt.IgnoreAspectRatio,
                         Qt.SmoothTransformation), Qt.DecorationRole)
        # 添加item到界面中
        self.dmodel.appendRow(item)

    def count(self):
        return self.dmodel.rowCount()

    def setCurrentRow(self, row):
        self.setCurrentIndex(self.dmodel.index(row, 0))

    def currentRow(self):
        return self.currentIndex().row()

    def updateGeometries(self):
        # 一次滑动20px
        super(ImageView, self).updateGeometries()
        self.verticalScrollBar().setSingleStep(ScrollPixel)

    def closeEvent(self, event):
        # 关闭预览窗口
        self.bigView.close()
        super(ImageView, self).closeEvent(event)

    def wheelEvent(self, event):
        # 修复滑动bug
        if self.flow() == QListView.LeftToRight:
            bar = self.horizontalScrollBar()
            value = ScrollPixel if event.angleDelta().y() < 0 else (
                0 - ScrollPixel)
            bar.setSliderPosition(bar.value() + value)
        else:
            super(ImageView, self).wheelEvent(event)

    def mouseDoubleClickEvent(self, event):
        # 列表双击,如果有item则进入item处理流程,否则调用打开图片功能
        index = self.indexAt(event.pos())
        if index and index.isValid():
            item = self.dmodel.itemFromIndex(index)
            if item:
                # 取出原图用来新窗口显示
                image = item.data(Qt.UserRole + 1)
                self.bigView.setPixmap(image)
                self.bigView.show()
            return
        super(ImageView, self).mouseDoubleClickEvent(event)
Example #34
0
class RefNodeSetsWidget(QObject):

    error = pyqtSignal(Exception)
    nodeset_added = pyqtSignal(str)
    nodeset_removed = pyqtSignal(str)

    def __init__(self, view):
        QObject.__init__(self, view)
        self.view = view
        self.model = QStandardItemModel()
        self.view.setModel(self.model)
        self.nodesets = []
        self.server_mgr = None
        self._nodeset_to_delete = None
        self.view.header().setSectionResizeMode(1)

        addNodeSetAction = QAction("Add Reference Node Set", self.model)
        addNodeSetAction.triggered.connect(self.add_nodeset)
        self.removeNodeSetAction = QAction("Remove Reference Node Set",
                                           self.model)
        self.removeNodeSetAction.triggered.connect(self.remove_nodeset)

        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.showContextMenu)
        self._contextMenu = QMenu()
        self._contextMenu.addAction(addNodeSetAction)
        self._contextMenu.addAction(self.removeNodeSetAction)

    @trycatchslot
    def add_nodeset(self):
        path, ok = QFileDialog.getOpenFileName(
            self.view,
            caption="Import OPC UA XML Node Set",
            filter="XML Files (*.xml *.XML)",
            directory=".")
        if not ok:
            return None
        name = os.path.basename(path)
        if name in self.nodesets:
            return
        try:
            self.server_mgr.import_xml(path)
        except Exception as ex:
            self.error.emit(ex)
            raise

        item = QStandardItem(name)
        self.model.appendRow([item])
        self.nodesets.append(name)
        self.view.expandAll()
        self.nodeset_added.emit(path)

    @trycatchslot
    def remove_nodeset(self):
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            return

        item = self.model.itemFromIndex(idx)
        name = item.text()
        self.nodesets.remove(name)
        self.model.removeRow(idx.row())
        self.nodeset_removed.emit(name)

    def set_server_mgr(self, server_mgr):
        self.server_mgr = server_mgr
        self.nodesets = []
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['Node Sets'])
        item = QStandardItem("Opc.Ua.NodeSet2.xml")
        item.setFlags(Qt.NoItemFlags)
        self.model.appendRow([item])
        self.view.expandAll()

    def clear(self):
        self.model.clear()

    @trycatchslot
    def showContextMenu(self, position):
        if not self.server_mgr:
            return
        idx = self.view.currentIndex()
        if not idx.isValid() or idx.row() == 0:
            self.removeNodeSetAction.setEnabled(False)
        else:
            self.removeNodeSetAction.setEnabled(True)
        self._contextMenu.exec_(self.view.viewport().mapToGlobal(position))
Example #35
0
class ProgramInfoFiles(QWidget, Ui_ProgramInfoFiles):
    def __init__(self, context, update_main_ui_state, set_widget_enabled, is_alive, show_upload_files_wizard, show_download_wizard):
        QWidget.__init__(self)

        self.setupUi(self)

        self.session                 = context.session
        self.script_manager          = context.script_manager
        self.program                 = context.program
        self.update_main_ui_state    = update_main_ui_state
        self.set_widget_enabled      = set_widget_enabled
        self.is_alive                = is_alive
        self.show_download_wizard    = show_download_wizard
        self.bin_directory           = posixpath.join(self.program.root_directory, 'bin')
        self.refresh_in_progress     = False
        self.any_refresh_in_progress = False # set from ProgramInfoMain.update_ui_state
        self.available_files         = []
        self.available_directories   = []
        self.folder_icon             = QIcon(load_pixmap('folder-icon.png'))
        self.file_icon               = QIcon(load_pixmap('file-icon.png'))
        self.tree_files_model        = QStandardItemModel(self)
        self.tree_files_model_header = ['Name', 'Size', 'Last Modified']
        self.tree_files_proxy_model  = FilesProxyModel(self)
        self.last_download_directory = get_home_path()

        self.tree_files_model.setHorizontalHeaderLabels(self.tree_files_model_header)
        self.tree_files_proxy_model.setSourceModel(self.tree_files_model)
        self.tree_files.setModel(self.tree_files_model)
        self.tree_files.setModel(self.tree_files_proxy_model)
        self.tree_files.setColumnWidth(0, 210)
        self.tree_files.setColumnWidth(1, 85)

        self.tree_files.selectionModel().selectionChanged.connect(self.update_ui_state)
        self.tree_files.activated.connect(self.rename_activated_file)
        self.button_upload_files.clicked.connect(show_upload_files_wizard)
        self.button_download_files.clicked.connect(self.download_selected_files)
        self.button_rename_file.clicked.connect(self.rename_selected_file)
        self.button_change_file_permissions.clicked.connect(self.change_permissions_of_selected_file)
        self.button_delete_files.clicked.connect(self.delete_selected_files)

        self.label_error.setVisible(False)

    def update_ui_state(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        self.set_widget_enabled(self.button_upload_files, not self.any_refresh_in_progress)
        self.set_widget_enabled(self.button_download_files, not self.any_refresh_in_progress and selection_count > 0)
        self.set_widget_enabled(self.button_rename_file, not self.any_refresh_in_progress and selection_count == 1)
        self.set_widget_enabled(self.button_change_file_permissions, not self.any_refresh_in_progress and selection_count == 1)
        self.set_widget_enabled(self.button_delete_files, not self.any_refresh_in_progress and selection_count > 0)

    def close_all_dialogs(self):
        pass

    def refresh_files_done(self):
        self.refresh_in_progress = False
        self.update_main_ui_state()

    def refresh_files(self):
        def cb_walk(result):
            okay, message = check_script_result(result, decode_stderr=True)

            if not okay:
                self.label_error.setText('<b>Error:</b> ' + html.escape(message))
                self.label_error.setVisible(True)
                self.refresh_files_done()
                return

            self.label_error.setVisible(False)

            def expand_async(data):
                try:
                    walk = json.loads(zlib.decompress(memoryview(data)).decode('utf-8'))
                except:
                    walk = None

                if walk == None or not isinstance(walk, dict):
                    available_files       = []
                    available_directories = []
                    walk                  = None
                else:
                    available_files, available_directories = expand_walk_to_lists(walk)

                return walk, available_files, available_directories

            def cb_expand_success(result):
                walk, available_files, available_directories = result

                self.available_files       = available_files
                self.available_directories = available_directories

                if walk != None:
                    expand_walk_to_model(walk, self.tree_files_model, self.folder_icon, self.file_icon)
                else:
                    self.label_error.setText('<b>Error:</b> Received invalid data')
                    self.label_error.setVisible(True)

                self.tree_files.header().setSortIndicator(0, Qt.AscendingOrder)
                self.refresh_files_done()

            def cb_expand_error():
                self.label_error.setText('<b>Error:</b> Internal async error')
                self.label_error.setVisible(True)
                self.refresh_files_done()

            async_call(expand_async, result.stdout, cb_expand_success, cb_expand_error)

        self.refresh_in_progress = True
        self.update_main_ui_state()

        width1 = self.tree_files.columnWidth(0)
        width2 = self.tree_files.columnWidth(1)

        self.tree_files_model.clear()
        self.tree_files_model.setHorizontalHeaderLabels(self.tree_files_model_header)
        self.tree_files.setColumnWidth(0, width1)
        self.tree_files.setColumnWidth(1, width2)

        self.script_manager.execute_script('walk', cb_walk,
                                           [self.bin_directory], max_length=1024*1024,
                                           decode_output_as_utf8=False)

    def get_directly_selected_name_items(self):
        selected_indexes    = self.tree_files.selectedIndexes()
        selected_name_items = []

        for selected_index in selected_indexes:
            if selected_index.column() == 0:
                mapped_index = self.tree_files_proxy_model.mapToSource(selected_index)
                selected_name_items.append(self.tree_files_model.itemFromIndex(mapped_index))

        return selected_name_items

    def download_selected_files(self):
        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) == 0:
            return

        downloads = []

        def expand(name_item):
            item_type = name_item.data(USER_ROLE_ITEM_TYPE)

            if item_type == ITEM_TYPE_DIRECTORY:
                for i in range(name_item.rowCount()):
                    expand(name_item.child(i, 0))
            elif item_type == ITEM_TYPE_FILE:
                filename = get_full_item_path(name_item)

                downloads.append(Download(filename, QDir.toNativeSeparators(filename)))

        for selected_name_item in selected_name_items:
            expand(selected_name_item)

        if len(downloads) == 0:
            return

        download_directory = get_existing_directory(get_main_window(), 'Download Files',
                                                    self.last_download_directory)

        if len(download_directory) == 0:
            return

        self.last_download_directory = download_directory

        self.show_download_wizard('files', download_directory, downloads)

    def rename_activated_file(self, index):
        if index.column() == 0 and not self.any_refresh_in_progress:
            mapped_index = self.tree_files_proxy_model.mapToSource(index)
            name_item    = self.tree_files_model.itemFromIndex(mapped_index)
            item_type    = name_item.data(USER_ROLE_ITEM_TYPE)

            # only rename files via activation, because directories are expanded
            if item_type == ITEM_TYPE_FILE:
                self.rename_item(name_item)

    def rename_selected_file(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        if selection_count != 1:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) != 1:
            return

        self.rename_item(selected_name_items[0])

    def rename_item(self, name_item):
        item_type = name_item.data(USER_ROLE_ITEM_TYPE)

        if item_type == ITEM_TYPE_FILE:
            title     = 'Rename File'
            type_name = 'file'
        else:
            title     = 'Rename Directory'
            type_name = 'directory'

        old_name = name_item.text()

        # get new name
        dialog = ExpandingInputDialog(get_main_window())
        dialog.setModal(True)
        dialog.setWindowTitle(title)
        dialog.setLabelText('Enter new {0} name:'.format(type_name))
        dialog.setInputMode(QInputDialog.TextInput)
        dialog.setTextValue(old_name)
        dialog.setOkButtonText('Rename')

        if dialog.exec_() != QDialog.Accepted:
            return

        new_name = dialog.textValue()

        if new_name == old_name:
            return

        # check that new name is valid
        if len(new_name) == 0 or new_name == '.' or new_name == '..' or '/' in new_name:
            QMessageBox.critical(get_main_window(), title + ' Error',
                                 'A {0} name cannot be empty, cannot be one dot [.], cannot be two dots [..] and cannot contain a forward slash [/].'
                                 .format(type_name))
            return

        # check that new name is not already in use
        name_item_parent = name_item.parent()

        if name_item_parent == None:
            name_item_parent = self.tree_files_model.invisibleRootItem()

        for i in range(name_item_parent.rowCount()):
            if new_name == name_item_parent.child(i).text():
                QMessageBox.critical(get_main_window(), title + ' Error',
                                     'The new {0} name is already in use.'.format(type_name))
                return

        absolute_old_name = posixpath.join(self.bin_directory, get_full_item_path(name_item))
        absolute_new_name = posixpath.join(posixpath.split(absolute_old_name)[0], new_name)

        def cb_rename(result):
            if not report_script_result(result, title + ' Error', 'Could not rename {0}'.format(type_name)):
                return

            name_item.setText(new_name)

            if self.tree_files.header().sortIndicatorSection() == 0:
                self.tree_files.header().setSortIndicator(0, self.tree_files.header().sortIndicatorOrder())

        self.script_manager.execute_script('rename', cb_rename,
                                           [absolute_old_name, absolute_new_name])

    def change_permissions_of_selected_file(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        if selection_count != 1:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) != 1:
            return

        name_item = selected_name_items[0]
        item_type = name_item.data(USER_ROLE_ITEM_TYPE)
        old_permissions = name_item.data(USER_ROLE_PERMISSIONS)

        if item_type == ITEM_TYPE_FILE:
            title     = 'Change File Permissions'
            type_name = 'file'
        else:
            title     = 'Change Directory Permissions'
            type_name = 'directory'

        dialog = ProgramInfoFilesPermissions(get_main_window(), title, old_permissions)

        if dialog.exec_() != QDialog.Accepted:
            return

        new_permissions = dialog.get_permissions()

        if new_permissions == (old_permissions & 0o777):
            return

        absolute_name = posixpath.join(self.bin_directory, get_full_item_path(name_item))

        def cb_change_permissions(result):
            if not report_script_result(result, title + ' Error', 'Could change {0} permissions'.format(type_name)):
                return

            name_item.setData(new_permissions, USER_ROLE_PERMISSIONS)

        self.script_manager.execute_script('change_permissions', cb_change_permissions,
                                           [absolute_name, str(new_permissions)])

    def delete_selected_files(self):
        button = QMessageBox.question(get_main_window(), 'Delete Files',
                                      'Irreversibly deleting selected files and directories.',
                                      QMessageBox.Ok, QMessageBox.Cancel)

        if not self.is_alive() or button != QMessageBox.Ok:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) == 0:
            return

        script_instance_ref = [None]

        def progress_canceled():
            script_instance = script_instance_ref[0]

            if script_instance == None:
                return

            self.script_manager.abort_script(script_instance)

        progress = ExpandingProgressDialog(self)
        progress.set_progress_text_visible(False)
        progress.setModal(True)
        progress.setWindowTitle('Delete Files')
        progress.setLabelText('Collecting files and directories to delete')
        progress.setRange(0, 0)
        progress.canceled.connect(progress_canceled)
        progress.show()

        files_to_delete = []
        dirs_to_delete  = []
        all_done        = False

        while not all_done:
            all_done = True

            for selected_name_item in list(selected_name_items):
                item_done = False
                parent = selected_name_item.parent()

                while not item_done and parent != None:
                    if parent in selected_name_items:
                        selected_name_items.remove(selected_name_item)
                        item_done = True
                    else:
                        parent = parent.parent()

                if item_done:
                    all_done = False
                    break

        for selected_name_item in selected_name_items:
            path      = get_full_item_path(selected_name_item)
            item_type = selected_name_item.data(USER_ROLE_ITEM_TYPE)

            if item_type == ITEM_TYPE_DIRECTORY:
                dirs_to_delete.append(posixpath.join(self.bin_directory, path))
            else:
                files_to_delete.append(posixpath.join(self.bin_directory, path))

        message = 'Deleting '

        if len(files_to_delete) == 1:
            message += '1 file '
        elif len(files_to_delete) > 1:
            message += '{0} files '.format(len(files_to_delete))

        if len(dirs_to_delete) == 1:
            if len(files_to_delete) > 0:
                message += 'and '

            message += '1 directory'
        elif len(dirs_to_delete) > 1:
            if len(files_to_delete) > 0:
                message += 'and '

            message += '{0} directories'.format(len(dirs_to_delete))

        progress.setLabelText(message)

        def cb_delete(result):
            script_instance = script_instance_ref[0]

            if script_instance != None:
                aborted = script_instance.abort
            else:
                aborted = False

            script_instance_ref[0] = None

            progress.cancel()
            self.refresh_files()

            if aborted:
                QMessageBox.information(get_main_window(), 'Delete Files',
                                        'Delete operation was aborted.')
                return

            report_script_result(result, 'Delete Files Error', 'Could not delete selected files/directories:')

        script_instance_ref[0] = self.script_manager.execute_script('delete', cb_delete,
                                                                    [json.dumps(files_to_delete), json.dumps(dirs_to_delete)],
                                                                    execute_as_user=True)
Example #36
0
class ApkList(DwarfListView):
    """ Displays installed APKs
    """

    onApkSelected = pyqtSignal(list, name='onApkSelected')

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

        self.adb = Adb()

        if not self.adb.available():
            return

        self.retrieve_thread = PackageRetrieveThread(self.adb)
        if self.retrieve_thread is not None:
            self.retrieve_thread.onAddPackage.connect(self._on_addpackage)

        if show_path:
            self.apk_model = QStandardItemModel(0, 2)
        else:
            self.apk_model = QStandardItemModel(0, 1)

        self.apk_model.setHeaderData(0, Qt.Horizontal, 'Name')

        if show_path:
            self.apk_model.setHeaderData(1, Qt.Horizontal, 'Path')

        self.setModel(self.apk_model)
        self.header().setSectionResizeMode(0, QHeaderView.ResizeToContents)

        self.doubleClicked.connect(self._on_apk_selected)

        if self.retrieve_thread is not None:
            if not self.retrieve_thread.isRunning():
                self.retrieve_thread.start()

    # ************************************************************************
    # **************************** Functions *********************************
    # ************************************************************************
    def refresh(self):
        """ Refresh Packages
        """
        if self.retrieve_thread is not None:
            if not self.retrieve_thread.isRunning():
                self.clear()
                self.retrieve_thread.start()

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    def _on_addpackage(self, package):
        if package:
            name = QStandardItem()
            name.setText(package[0])

            if self.apk_model.columnCount() == 2:
                path = QStandardItem()
                path.setText(package[1])

                self.apk_model.appendRow([name, path])
            else:
                self.apk_model.appendRow([name])

    def _on_apk_selected(self, model_index):
        item = self.apk_model.itemFromIndex(model_index).row()
        if item != -1:
            package = self.apk_model.item(item, 0).text()
            if self.apk_model.columnCount() == 2:
                path = self.apk_model.item(item, 1).text()
                self.onApkSelected.emit([package, path])
            else:
                self.onApkSelected.emit([package, None])
Example #37
0
class ProgramInfoFiles(QWidget, Ui_ProgramInfoFiles):
    def __init__(self, context, update_main_ui_state, set_widget_enabled,
                 is_alive, show_upload_files_wizard, show_download_wizard):
        QWidget.__init__(self)

        self.setupUi(self)

        self.session = context.session
        self.script_manager = context.script_manager
        self.program = context.program
        self.update_main_ui_state = update_main_ui_state
        self.set_widget_enabled = set_widget_enabled
        self.is_alive = is_alive
        self.show_download_wizard = show_download_wizard
        self.bin_directory = posixpath.join(self.program.root_directory, 'bin')
        self.refresh_in_progress = False
        self.any_refresh_in_progress = False  # set from ProgramInfoMain.update_ui_state
        self.available_files = []
        self.available_directories = []
        self.folder_icon = QIcon(load_pixmap('folder-icon.png'))
        self.file_icon = QIcon(load_pixmap('file-icon.png'))
        self.tree_files_model = QStandardItemModel(self)
        self.tree_files_model_header = ['Name', 'Size', 'Last Modified']
        self.tree_files_proxy_model = FilesProxyModel(self)
        self.last_download_directory = get_home_path()

        self.tree_files_model.setHorizontalHeaderLabels(
            self.tree_files_model_header)
        self.tree_files_proxy_model.setSourceModel(self.tree_files_model)
        self.tree_files.setModel(self.tree_files_model)
        self.tree_files.setModel(self.tree_files_proxy_model)
        self.tree_files.setColumnWidth(0, 210)
        self.tree_files.setColumnWidth(1, 85)

        self.tree_files.selectionModel().selectionChanged.connect(
            self.update_ui_state)
        self.tree_files.activated.connect(self.rename_activated_file)
        self.button_upload_files.clicked.connect(show_upload_files_wizard)
        self.button_download_files.clicked.connect(
            self.download_selected_files)
        self.button_rename_file.clicked.connect(self.rename_selected_file)
        self.button_change_file_permissions.clicked.connect(
            self.change_permissions_of_selected_file)
        self.button_delete_files.clicked.connect(self.delete_selected_files)

        self.label_error.setVisible(False)

    def update_ui_state(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        self.set_widget_enabled(self.button_upload_files,
                                not self.any_refresh_in_progress)
        self.set_widget_enabled(
            self.button_download_files, not self.any_refresh_in_progress
            and selection_count > 0)
        self.set_widget_enabled(
            self.button_rename_file, not self.any_refresh_in_progress
            and selection_count == 1)
        self.set_widget_enabled(
            self.button_change_file_permissions,
            not self.any_refresh_in_progress and selection_count == 1)
        self.set_widget_enabled(
            self.button_delete_files, not self.any_refresh_in_progress
            and selection_count > 0)

    def close_all_dialogs(self):
        pass

    def refresh_files_done(self):
        self.refresh_in_progress = False
        self.update_main_ui_state()

    def refresh_files(self):
        def cb_walk(result):
            okay, message = check_script_result(result, decode_stderr=True)

            if not okay:
                self.label_error.setText('<b>Error:</b> ' +
                                         html.escape(message))
                self.label_error.setVisible(True)
                self.refresh_files_done()
                return

            self.label_error.setVisible(False)

            def expand_async(data):
                try:
                    walk = json.loads(
                        zlib.decompress(memoryview(data)).decode('utf-8'))
                except:
                    walk = None

                if walk == None or not isinstance(walk, dict):
                    available_files = []
                    available_directories = []
                    walk = None
                else:
                    available_files, available_directories = expand_walk_to_lists(
                        walk)

                return walk, available_files, available_directories

            def cb_expand_success(result):
                walk, available_files, available_directories = result

                self.available_files = available_files
                self.available_directories = available_directories

                if walk != None:
                    expand_walk_to_model(walk, self.tree_files_model,
                                         self.folder_icon, self.file_icon)
                else:
                    self.label_error.setText(
                        '<b>Error:</b> Received invalid data')
                    self.label_error.setVisible(True)

                self.tree_files.header().setSortIndicator(0, Qt.AscendingOrder)
                self.refresh_files_done()

            def cb_expand_error():
                self.label_error.setText('<b>Error:</b> Internal async error')
                self.label_error.setVisible(True)
                self.refresh_files_done()

            async_call(expand_async, result.stdout, cb_expand_success,
                       cb_expand_error)

        self.refresh_in_progress = True
        self.update_main_ui_state()

        width1 = self.tree_files.columnWidth(0)
        width2 = self.tree_files.columnWidth(1)

        self.tree_files_model.clear()
        self.tree_files_model.setHorizontalHeaderLabels(
            self.tree_files_model_header)
        self.tree_files.setColumnWidth(0, width1)
        self.tree_files.setColumnWidth(1, width2)

        self.script_manager.execute_script('walk',
                                           cb_walk, [self.bin_directory],
                                           max_length=1024 * 1024,
                                           decode_output_as_utf8=False)

    def get_directly_selected_name_items(self):
        selected_indexes = self.tree_files.selectedIndexes()
        selected_name_items = []

        for selected_index in selected_indexes:
            if selected_index.column() == 0:
                mapped_index = self.tree_files_proxy_model.mapToSource(
                    selected_index)
                selected_name_items.append(
                    self.tree_files_model.itemFromIndex(mapped_index))

        return selected_name_items

    def download_selected_files(self):
        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) == 0:
            return

        downloads = []

        def expand(name_item):
            item_type = name_item.data(USER_ROLE_ITEM_TYPE)

            if item_type == ITEM_TYPE_DIRECTORY:
                for i in range(name_item.rowCount()):
                    expand(name_item.child(i, 0))
            elif item_type == ITEM_TYPE_FILE:
                filename = get_full_item_path(name_item)

                downloads.append(
                    Download(filename, QDir.toNativeSeparators(filename)))

        for selected_name_item in selected_name_items:
            expand(selected_name_item)

        if len(downloads) == 0:
            return

        download_directory = get_existing_directory(
            get_main_window(), 'Download Files', self.last_download_directory)

        if len(download_directory) == 0:
            return

        self.last_download_directory = download_directory

        self.show_download_wizard('files', download_directory, downloads)

    def rename_activated_file(self, index):
        if index.column() == 0 and not self.any_refresh_in_progress:
            mapped_index = self.tree_files_proxy_model.mapToSource(index)
            name_item = self.tree_files_model.itemFromIndex(mapped_index)
            item_type = name_item.data(USER_ROLE_ITEM_TYPE)

            # only rename files via activation, because directories are expanded
            if item_type == ITEM_TYPE_FILE:
                self.rename_item(name_item)

    def rename_selected_file(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        if selection_count != 1:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) != 1:
            return

        self.rename_item(selected_name_items[0])

    def rename_item(self, name_item):
        item_type = name_item.data(USER_ROLE_ITEM_TYPE)

        if item_type == ITEM_TYPE_FILE:
            title = 'Rename File'
            type_name = 'file'
        else:
            title = 'Rename Directory'
            type_name = 'directory'

        old_name = name_item.text()

        # get new name
        dialog = ExpandingInputDialog(get_main_window())
        dialog.setModal(True)
        dialog.setWindowTitle(title)
        dialog.setLabelText('Enter new {0} name:'.format(type_name))
        dialog.setInputMode(QInputDialog.TextInput)
        dialog.setTextValue(old_name)
        dialog.setOkButtonText('Rename')

        if dialog.exec_() != QDialog.Accepted:
            return

        new_name = dialog.textValue()

        if new_name == old_name:
            return

        # check that new name is valid
        if len(
                new_name
        ) == 0 or new_name == '.' or new_name == '..' or '/' in new_name:
            QMessageBox.critical(
                get_main_window(), title + ' Error',
                'A {0} name cannot be empty, cannot be one dot [.], cannot be two dots [..] and cannot contain a forward slash [/].'
                .format(type_name))
            return

        # check that new name is not already in use
        name_item_parent = name_item.parent()

        if name_item_parent == None:
            name_item_parent = self.tree_files_model.invisibleRootItem()

        for i in range(name_item_parent.rowCount()):
            if new_name == name_item_parent.child(i).text():
                QMessageBox.critical(
                    get_main_window(), title + ' Error',
                    'The new {0} name is already in use.'.format(type_name))
                return

        absolute_old_name = posixpath.join(self.bin_directory,
                                           get_full_item_path(name_item))
        absolute_new_name = posixpath.join(
            posixpath.split(absolute_old_name)[0], new_name)

        def cb_rename(result):
            if not report_script_result(
                    result, title + ' Error',
                    'Could not rename {0}'.format(type_name)):
                return

            name_item.setText(new_name)

            if self.tree_files.header().sortIndicatorSection() == 0:
                self.tree_files.header().setSortIndicator(
                    0,
                    self.tree_files.header().sortIndicatorOrder())

        self.script_manager.execute_script(
            'rename', cb_rename, [absolute_old_name, absolute_new_name])

    def change_permissions_of_selected_file(self):
        selection_count = len(self.tree_files.selectionModel().selectedRows())

        if selection_count != 1:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) != 1:
            return

        name_item = selected_name_items[0]
        item_type = name_item.data(USER_ROLE_ITEM_TYPE)
        old_permissions = name_item.data(USER_ROLE_PERMISSIONS)

        if item_type == ITEM_TYPE_FILE:
            title = 'Change File Permissions'
            type_name = 'file'
        else:
            title = 'Change Directory Permissions'
            type_name = 'directory'

        dialog = ProgramInfoFilesPermissions(get_main_window(), title,
                                             old_permissions)

        if dialog.exec_() != QDialog.Accepted:
            return

        new_permissions = dialog.get_permissions()

        if new_permissions == (old_permissions & 0o777):
            return

        absolute_name = posixpath.join(self.bin_directory,
                                       get_full_item_path(name_item))

        def cb_change_permissions(result):
            if not report_script_result(
                    result, title + ' Error',
                    'Could change {0} permissions'.format(type_name)):
                return

            name_item.setData(new_permissions, USER_ROLE_PERMISSIONS)

        self.script_manager.execute_script(
            'change_permissions', cb_change_permissions,
            [absolute_name, str(new_permissions)])

    def delete_selected_files(self):
        button = QMessageBox.question(
            get_main_window(), 'Delete Files',
            'Irreversibly deleting selected files and directories.',
            QMessageBox.Ok, QMessageBox.Cancel)

        if not self.is_alive() or button != QMessageBox.Ok:
            return

        selected_name_items = self.get_directly_selected_name_items()

        if len(selected_name_items) == 0:
            return

        script_instance_ref = [None]

        def progress_canceled():
            script_instance = script_instance_ref[0]

            if script_instance == None:
                return

            self.script_manager.abort_script(script_instance)

        progress = ExpandingProgressDialog(self)
        progress.set_progress_text_visible(False)
        progress.setModal(True)
        progress.setWindowTitle('Delete Files')
        progress.setLabelText('Collecting files and directories to delete')
        progress.setRange(0, 0)
        progress.canceled.connect(progress_canceled)
        progress.show()

        files_to_delete = []
        dirs_to_delete = []
        all_done = False

        while not all_done:
            all_done = True

            for selected_name_item in list(selected_name_items):
                item_done = False
                parent = selected_name_item.parent()

                while not item_done and parent != None:
                    if parent in selected_name_items:
                        selected_name_items.remove(selected_name_item)
                        item_done = True
                    else:
                        parent = parent.parent()

                if item_done:
                    all_done = False
                    break

        for selected_name_item in selected_name_items:
            path = get_full_item_path(selected_name_item)
            item_type = selected_name_item.data(USER_ROLE_ITEM_TYPE)

            if item_type == ITEM_TYPE_DIRECTORY:
                dirs_to_delete.append(posixpath.join(self.bin_directory, path))
            else:
                files_to_delete.append(posixpath.join(self.bin_directory,
                                                      path))

        message = 'Deleting '

        if len(files_to_delete) == 1:
            message += '1 file '
        elif len(files_to_delete) > 1:
            message += '{0} files '.format(len(files_to_delete))

        if len(dirs_to_delete) == 1:
            if len(files_to_delete) > 0:
                message += 'and '

            message += '1 directory'
        elif len(dirs_to_delete) > 1:
            if len(files_to_delete) > 0:
                message += 'and '

            message += '{0} directories'.format(len(dirs_to_delete))

        progress.setLabelText(message)

        def cb_delete(result):
            script_instance = script_instance_ref[0]

            if script_instance != None:
                aborted = script_instance.abort
            else:
                aborted = False

            script_instance_ref[0] = None

            progress.cancel()
            self.refresh_files()

            if aborted:
                QMessageBox.information(get_main_window(), 'Delete Files',
                                        'Delete operation was aborted.')
                return

            report_script_result(
                result, 'Delete Files Error',
                'Could not delete selected files/directories:')

        script_instance_ref[0] = self.script_manager.execute_script(
            'delete',
            cb_delete,
            [json.dumps(files_to_delete),
             json.dumps(dirs_to_delete)],
            execute_as_user=True)
Example #38
0
class SearchPanel(QWidget):
    """ SearchPanel
    """
    def __init__(self, parent=None, show_progress_dlg=False):
        super(SearchPanel, self).__init__(parent=parent)
        self._app_window = parent

        if self._app_window.dwarf is None:
            print('SearchPanel created before Dwarf exists')
            return

        self._app_window.dwarf.onMemoryScanResult.connect(
            self._on_search_result)

        self._app_window.dwarf.onSearchableRanges.connect(self._on_setranges)

        self._ranges_model = None
        self._result_model = None

        self._blocking_search = show_progress_dlg
        self.progress = None
        self._pattern_length = 0

        self._search_results = []

        self.setContentsMargins(0, 0, 0, 0)

        main_wrap = QVBoxLayout()
        main_wrap.setContentsMargins(1, 1, 1, 1)

        wrapping_wdgt = QWidget()
        wrapping_wdgt.setContentsMargins(10, 10, 10, 10)
        v_box = QVBoxLayout(wrapping_wdgt)
        v_box.setContentsMargins(0, 0, 0, 0)
        self.input = QLineEdit()
        self.input.setPlaceholderText(
            'search for a sequence of bytes in hex format: deadbeef123456aabbccddeeff...'
        )
        v_box.addWidget(self.input)

        self.check_all_btn = QPushButton('check all')
        self.check_all_btn.clicked.connect(self._on_click_check_all)
        self.uncheck_all_btn = QPushButton('uncheck all')
        self.uncheck_all_btn.clicked.connect(self._on_click_uncheck_all)
        self.search_btn = QPushButton('search')
        self.search_btn.clicked.connect(self._on_click_search)

        h_box = QHBoxLayout()
        h_box.addWidget(self.check_all_btn)
        h_box.addWidget(self.uncheck_all_btn)
        h_box.addWidget(self.search_btn)
        v_box.addLayout(h_box)

        main_wrap.addWidget(wrapping_wdgt)

        self.ranges = DwarfListView(self)
        self.ranges.clicked.connect(self._on_show_results)
        self.results = DwarfListView(self)
        self.results.setVisible(False)

        h_box = QHBoxLayout()
        h_box.setContentsMargins(0, 0, 0, 0)
        h_box.addWidget(self.ranges)
        h_box.addWidget(self.results)
        main_wrap.addLayout(h_box)

        main_wrap.setSpacing(0)

        self.setLayout(main_wrap)

        self._setup_models()
        self._app_window.dwarf.dwarf_api('updateSearchableRanges')

    # ************************************************************************
    # **************************** Functions *********************************
    # ************************************************************************
    def _setup_models(self):
        self._ranges_model = QStandardItemModel(0, 7)

        # just replicate ranges panel model
        self._ranges_model.setHeaderData(
            0, Qt.Horizontal, 'x'
        )  # TODO: replace with checkbox in header - remove checkall btns
        self._ranges_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(1, Qt.Horizontal, 'Address')
        self._ranges_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(2, Qt.Horizontal, 'Size')
        self._ranges_model.setHeaderData(2, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(3, Qt.Horizontal, 'Protection')
        self._ranges_model.setHeaderData(3, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(4, Qt.Horizontal, 'FileOffset')
        self._ranges_model.setHeaderData(4, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(5, Qt.Horizontal, 'FileSize')
        self._ranges_model.setHeaderData(5, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(6, Qt.Horizontal, 'FilePath')

        self.ranges.setModel(self._ranges_model)
        self.ranges.header().setSectionResizeMode(0,
                                                  QHeaderView.ResizeToContents)
        self.ranges.header().setSectionResizeMode(1,
                                                  QHeaderView.ResizeToContents)
        self.ranges.header().setSectionResizeMode(2,
                                                  QHeaderView.ResizeToContents)
        self.ranges.header().setSectionResizeMode(3,
                                                  QHeaderView.ResizeToContents)
        self.ranges.header().setSectionResizeMode(4,
                                                  QHeaderView.ResizeToContents)
        self.ranges.header().setSectionResizeMode(5,
                                                  QHeaderView.ResizeToContents)

        self.ranges.doubleClicked.connect(self._on_range_dblclick)

        # setup results model
        self._result_model = QStandardItemModel(0, 1)
        self._result_model.setHeaderData(0, Qt.Horizontal, 'Address')
        self.results.setModel(self._result_model)
        self.results.doubleClicked.connect(self._on_double_clicked)

    def _on_setranges(self, ranges):
        """ Fills Rangelist with Data
        """
        if self._ranges_model.rowCount():
            return

        self.ranges.header().setSectionResizeMode(0, QHeaderView.Fixed)
        if isinstance(ranges, list):
            self._ranges_model.removeRows(0, self._ranges_model.rowCount())
            for range_entry in ranges:
                if 'protection' in range_entry and isinstance(
                        range_entry['protection'], str):
                    if 'r' not in range_entry['protection']:
                        # skip not readable range
                        continue

                else:
                    continue
                # create items to add
                str_frmt = ''
                if self.ranges._uppercase_hex:
                    str_frmt = '0x{0:X}'
                else:
                    str_frmt = '0x{0:x}'

                addr = QStandardItem()
                addr.setTextAlignment(Qt.AlignCenter)
                addr.setText(str_frmt.format(int(range_entry['base'], 16)))

                size = QStandardItem()
                size.setTextAlignment(Qt.AlignRight)
                size.setText("{0:,d}".format(int(range_entry['size'])))

                protection = QStandardItem()
                protection.setTextAlignment(Qt.AlignCenter)
                protection.setText(range_entry['protection'])

                file_path = None
                file_addr = None
                file_size = None

                if len(range_entry) > 3:
                    if range_entry['file']['path']:
                        file_path = QStandardItem()
                        file_path.setText(range_entry['file']['path'])

                    if range_entry['file']['offset']:
                        file_addr = QStandardItem()
                        file_addr.setTextAlignment(Qt.AlignCenter)
                        file_addr.setText(
                            str_frmt.format(range_entry['file']['offset']))

                    if range_entry['file']['size']:
                        file_size = QStandardItem()
                        file_size.setTextAlignment(Qt.AlignRight)
                        file_size.setText("{0:,d}".format(
                            int(range_entry['file']['size'])))

                checkbox = QStandardItem()
                checkbox.setCheckable(True)

                self._ranges_model.appendRow([
                    checkbox, addr, size, protection, file_addr, file_size,
                    file_path
                ])

    # ************************************************************************
    # **************************** Handlers **********************************
    # ************************************************************************
    def _on_range_dblclick(self, model_index):
        item = self._ranges_model.itemFromIndex(model_index)
        if item:
            if self._ranges_model.item(model_index.row(),
                                       0).checkState() != Qt.Checked:
                self._ranges_model.item(model_index.row(),
                                        0).setCheckState(Qt.Checked)
            else:
                self._ranges_model.item(model_index.row(),
                                        0).setCheckState(Qt.Unchecked)

    def _on_click_check_all(self):
        for i in range(self._ranges_model.rowCount()):
            self._ranges_model.item(i, 0).setCheckState(Qt.Checked)

    def _on_click_uncheck_all(self):
        for i in range(self._ranges_model.rowCount()):
            self._ranges_model.item(i, 0).setCheckState(Qt.Unchecked)

    def _on_double_clicked(self, model_index):
        item = self._result_model.itemFromIndex(model_index)
        if item:
            self._app_window.jump_to_address(
                self._result_model.item(model_index.row(), 0).text())

    def _on_click_search(self):
        pattern = self.input.text()
        if pattern == '':
            return 1

        # check if we already provide a hex string as input
        try:
            test = pattern.replace(' ', '')
            int(test, 16)
            pattern = test
        except ValueError:
            # search for string
            pattern = binascii.hexlify(pattern.encode('utf8')).decode('utf8')

        ranges = []
        self._search_results = []
        for i in range(self._ranges_model.rowCount()):
            item = self._ranges_model.item(i, 0)
            if item.checkState() == Qt.Checked:
                addr = self._ranges_model.item(i, 1)
                size = self._ranges_model.item(i, 2)
                ranges.append([addr.text(), size.text()])

        if len(ranges) == 0:
            return 1

        status_message = 'searching...'

        if self._blocking_search:
            self.progress = utils.progress_dialog(status_message)
            self.progress.forceShow()

        self._app_window.show_progress(status_message)
        self.input.setEnabled(False)
        self.search_btn.setEnabled(False)
        self.check_all_btn.setEnabled(False)
        self.uncheck_all_btn.setEnabled(False)

        self._pattern_length = len(pattern) * .5

        search_thread = SearchThread(self._app_window.dwarf, self)
        search_thread.onCmdCompleted.connect(self._on_search_complete)
        search_thread.onError.connect(self._on_search_error)
        search_thread.pattern = pattern
        search_thread.ranges = ranges
        search_thread.start()

    def _on_search_result(self, data):
        self._search_results.append(data)

    def _on_search_complete(self):
        self.input.setEnabled(True)
        self.search_btn.setEnabled(True)
        self.check_all_btn.setEnabled(True)
        self.uncheck_all_btn.setEnabled(True)
        self._app_window.hide_progress()
        if self._blocking_search:
            self.progress.cancel()

        self._ranges_model.removeColumns(4, 3)
        self._ranges_model.setHeaderData(3, Qt.Horizontal, 'Search Results')
        self._ranges_model.setHeaderData(3, Qt.Horizontal, None,
                                         Qt.TextAlignmentRole)

        results_count = 0
        is_selected = False
        for i in range(self._ranges_model.rowCount()):
            item = self._ranges_model.item(i, 0)
            if item.checkState() == Qt.Checked:
                item.setCheckState(Qt.Unchecked)
                if not is_selected:
                    is_selected = True
                    self.ranges.setCurrentIndex(self._ranges_model.index(i, 0))
            else:
                self._search_results.insert(i, None)
                self._ranges_model.item(i, 3).setText('')
                self._ranges_model.item(i, 3).setTextAlignment(Qt.AlignLeft)
                continue

            if len(self._search_results[i]):
                results_count += len(self._search_results[i])
                self._ranges_model.item(i, 3).setText('Matches: {0}'.format(
                    len(self._search_results[i])))
                self._ranges_model.item(i, 3).setTextAlignment(Qt.AlignLeft)
            else:
                self._ranges_model.item(i, 3).setText('')
                self._ranges_model.item(i, 3).setTextAlignment(Qt.AlignLeft)

        self._app_window.set_status_text(
            'Search complete: {0} matches'.format(results_count))
        if results_count:
            for i in self._search_results:
                if i and len(i):
                    self.results.setVisible(True)
                    for result in i:
                        self._result_model.appendRow(
                            QStandardItem(result['address']))

                    break

    def _on_search_error(self, msg):
        utils.show_message_box(msg)

    def _on_show_results(self):
        if self._search_results:
            self.results.clear()
            if self._app_window.debug_panel.memory_panel:
                self._app_window.debug_panel.memory_panel.remove_highlights(
                    'search')
            selected_index = self.ranges.selectionModel().currentIndex().row()
            if selected_index is not None:
                item_txt = self._ranges_model.item(selected_index, 3).text()
                if item_txt == '':
                    return

                for result in self._search_results[selected_index]:
                    self._result_model.appendRow(
                        QStandardItem(result['address']))

                    # TODO: fix hexview highlights performance
                    """
Example #39
0
class _RegionTree(object):
    def __init__(self):
        self.view = None
        self.model = None

    def on_clicked_region(self):
        widgets_map['create_vault_button'].set_enabled(True)

    def on_clicked_vault(self):
        region = self.get_selected_region()
        vault = self.get_selected_vault()
        inventory = Inventories.get_inventory(region, vault)
        FilesTable.display_inventory(region, vault, inventory)

    def on_clicked(self, index: QModelIndex):
        widgets_map['create_vault_button'].set_enabled(False)

        if index.parent().isValid():
            self.on_clicked_vault()
        else:
            self.on_clicked_region()

    def get_selected_region(self):
        index = self.view.selectedIndexes()[0]

        if index.parent().isValid():
            region_name = index.parent().data()
        else:
            region_name = index.data()

        return regions.get_by_name(region_name)

    def get_selected_vault(self):
        index = self.view.selectedIndexes()[0]
        if not index.parent().isValid():
            return None
        return index.data()

    def initialize(self, window: QMainWindow):
        self.view: QTreeView = cast(QTreeView,
                                    window.findChild(QTreeView, 'treeView'))

        self.view.clicked.connect(self.on_clicked)
        self.view.customContextMenuRequested.connect(self.open_menu)

        self.model = QStandardItemModel()
        root: QStandardItem = self.model.invisibleRootItem()
        self.view.setModel(self.model)

        for region in REGIONS:
            item = QStandardItem(region.name)
            root.appendRow(item)
            if keys.Keys.has_keys():
                TaskManager.add_task(ListVaultsTask(region))

    def open_menu(self, position):
        level = 0

        indexes = self.view.selectedIndexes()
        if len(indexes) > 0:
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1

        menu = QMenu()
        if level == 1:
            get_inventory_action = QAction("Request last inventory", self.view)
            get_inventory_action.triggered.connect(self.get_inventory_action)
            menu.addAction(get_inventory_action)

        menu.exec(self.view.viewport().mapToGlobal(position))

    def add_vault(self, region, vault_name):
        root: QModelIndex = self.view.rootIndex()
        child_count = self.model.rowCount(root)
        parent = QModelIndex()

        for i in range(child_count):
            row = self.model.index(i, 0, parent)
            cur_region = row.data()
            if region.name == cur_region:
                break
        else:
            raise ValueError(f"Failed to find region {region}")

        item = QStandardItem(vault_name)
        self.model.itemFromIndex(row).appendRow(item)

        self.view.expandAll()

        # Refresh model to see new rows
        self.model.rowsInserted.emit(parent, 0, child_count)

    def get_inventory_action(self):
        vault_name = self.view.selectedIndexes()[0].data()
        region = self.get_selected_region()

        TaskManager.add_task(GetInventoryTask(region, vault_name))
Example #40
0
class CreateWidget(QWidget):
    """
    This class is a subclass of `QWidget` which opens a widget used to create transforms

    :param parent: The parent widget
    :type parent: QWidget
    """

    tfCreated = pyqtSignal()

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

        self.parent = parent
        self.type_ = None

        load_ui('create_widget.ui', self)
        self.setAttribute(Qt.WA_DeleteOnClose, True)
        self.layout().setSizeConstraint(QLayout.SetFixedSize)

        ref_text = str(self.parent.text()).split('[', 1)[0].strip()
        self.referenceText.setText(ref_text)
        self.parentText.setText(self.parent.text())

        self.patternWidget.close()

        self.scatter_model = QStandardItemModel(self.patternWidget)
        self.scatterListView.setModel(self.scatter_model)

        self.dialogButton.accepted.connect(self._on_accepted)
        self.dialogButton.rejected.connect(self._on_rejected)

        self.typeBox.currentIndexChanged.connect(self._on_type_index_changed)
        self.patternBox.currentIndexChanged.connect(
            self._on_pattern_index_changed)

        self.addPointButton.clicked.connect(self._on_add_scatter_point)
        self.removePointButton.clicked.connect(self._on_remove_scatter_point)

        lin_les = [self.numPointsText, self.stepSizeText, self.lengthText]
        rect_x_les = [
            self.numPointsXText, self.stepSizesXText, self.lengthsXText
        ]
        rect_y_les = [
            self.numPointsYText, self.stepSizesYText, self.lengthsYText
        ]

        for le in lin_les:
            le.textChanged.connect(lambda: self._restrict_num_fields(lin_les))

        for le in rect_x_les:
            le.textChanged.connect(
                lambda: self._restrict_num_fields(rect_x_les))

        for le in rect_y_les:
            le.textChanged.connect(
                lambda: self._restrict_num_fields(rect_y_les))

    @staticmethod
    def _restrict_num_fields(line_edits):
        """
        This function specifically disables QLineEdit widgets of a parent widget when the required number of
        field have been assigned

        :param line_edits: A list of QLineEdit widgets
        :type line_edits: list
        """

        count = len(line_edits) - 1

        for le in line_edits:
            if len(le.text()) > 0:
                count -= 1

        if count == 0:
            for le in line_edits:
                if len(le.text()) == 0:
                    le.setEnabled(False)
        else:
            for le in line_edits:
                le.setEnabled(True)

    def _on_add_scatter_point(self):
        """
        This callback function updates the scatterListView model when a new entry is
        entered and added (i.e. a new position)
        """

        in_ = [self.pointXText, self.pointYText, self.pointZText]

        for le in in_:
            self._handle_empty_line(le)

        point = [
            float(in_[0].text()),
            float(in_[1].text()),
            float(in_[2].text())
        ]

        item = QStandardItem(str(point))
        self.scatter_model.appendRow(item)

    def _on_remove_scatter_point(self):
        """
        This callback function updates the scatterListView model when an item is removed
        """

        index = self.scatterListView.selectionModel().currentIndex()
        cur_selection = self.scatter_model.itemFromIndex(index)

        if not cur_selection:
            return

        self.scatter_model.removeRow(cur_selection.row())

    def _on_type_index_changed(self):
        """
        This callback function administers the opening and closing of the appropriate widgets
        when a type (i.e. transform or pattern) is chosen in the typeBox dropdown widget
        """

        self.patternWidget.close()

        if self.typeBox.currentText() == 'Pattern':
            self.patternWidget.show()
            self._on_pattern_index_changed()

            return

    def _on_pattern_index_changed(self):
        """
        This callback function administers the opening and closing of the appropriate widgets
        when a pattern (e.g. linear, rectangular) is chosen in the patternBox dropdown widget
        """

        wdgs = [
            self.linearWidget, self.rectangularWidget, self.circularWidget,
            self.scatterWidget
        ]

        for w in wdgs:
            w.close()

        if self.patternBox.currentText() == 'Linear':
            self.linearWidget.show()
        elif self.patternBox.currentText() == 'Rectangular':
            self.rectangularWidget.show()
        elif self.patternBox.currentText() == 'Circular':
            self.circularWidget.show()
        elif self.patternBox.currentText() == 'Scatter':
            self.scatterWidget.show()

    def _get_transform_pose(self):
        """
        This function retrieves the pose information of root transform from the transformWidget widget,
        handles empty entries, and casts the data to the appropriate data type

        :return: The position and orientation of the root transform
        :rtype: tuple
        """

        in_ = [
            self.xText, self.yText, self.zText, self.qxText, self.qyText,
            self.qzText, self.qwText
        ]

        for le in in_:
            self._handle_empty_line(le)

        xyz = [
            float(in_[0].text()),
            float(in_[1].text()),
            float(in_[2].text())
        ]

        q = [
            float(in_[3].text()),
            float(in_[4].text()),
            float(in_[5].text()),
            float(in_[6].text())
        ]

        return xyz, q

    def _on_accepted(self):
        """
        This callback function handles the creation of the requested transforms when 'OK' (accepted)
        is selected in the dialogButton widget
        """

        empty = self.check_required_field_empty(self.nameText)
        empty = self.check_required_field_empty(self.referenceText) or empty

        if empty:
            return

        tf_pose = self._get_transform_pose()

        if self.typeBox.currentText() == 'Transform':
            pmc.create_transform(self.nameText.text(),
                                 self.parent.data()['id'],
                                 self.referenceText.text(), tf_pose[0],
                                 tf_pose[1])
        elif self.typeBox.currentText() == 'Pattern':
            self._create_pattern(self.patternBox.currentText(), tf_pose[0],
                                 tf_pose[1])

        self.tfCreated.emit()
        self._on_rejected()

    def _on_rejected(self):
        """
        This callback function handles the resetting of widget and closing of the widget when 'Cancel' (rejected)
        is selected in the dialogButton widget
        """

        wgds = self.findChildren(QLineEdit)
        wgds.extend(self.findChildren(QComboBox))

        for w in wgds:
            w.clear()

        for c in self.findChildren(QCheckBox):
            c.setChecked(False)

        self.close()

    def _create_pattern(self, type_, xyz, q):
        """
        This function calls the appropriate function, responsible for the creation of a transform pattern,
        from the supplied type

        :param type_: The type of pattern to be created
        :type type_: str
        :param xyz: The position of the root transform (parent)
        :type xyz: list
        :param q: The orientation of the root transform (parent)
        :type q: list
        """

        for le in self.patternWidget.findChildren(QLineEdit):
            le.setEnabled(True)

        args = [str(self.nameText.text()), self.parent.data()['id'], xyz, q]

        if type_ == 'Linear':
            self._create_linear_pattern(args)
        elif type_ == 'Rectangular':
            self._create_rectangular_pattern(args)
        elif type_ == 'Scatter':
            self._create_scatter_pattern(args)
        elif type_ == 'Circular':
            self._create_circular_pattern(args)
        else:
            raise ValueError('Pattern type is not implemented')

    def _create_linear_pattern(self, args):
        """
        This function creates a linear pattern of transforms under the parent transform

        :param args: data about the parent transform
        :type args: list
        """

        in_ = [self.numPointsText, self.stepSizeText, self.lengthText]

        for le in in_:
            self._handle_empty_line(le)

        args.extend(
            [int(in_[0].text()),
             float(in_[1].text()),
             float(in_[2].text())])

        pmc.create_linear_pattern(*args)

    def _create_rectangular_pattern(self, args):
        """
        This function creates a rectangular pattern of transforms under the parent transform

        :param args: data about the parent transform
        :type args: list
        """

        in_ = [
            self.numPointsXText, self.numPointsYText, self.stepSizesXText,
            self.stepSizesYText, self.lengthsXText, self.lengthsYText
        ]

        for le in in_:
            self._handle_empty_line(le)

        args.extend([[int(in_[0].text()),
                      int(in_[1].text())],
                     [float(in_[2].text()),
                      float(in_[3].text())],
                     [float(in_[4].text()),
                      float(in_[5].text())]])

        pmc.create_rectangular_pattern(*args)

    def _create_circular_pattern(self, args):
        """
        This function creates a circular pattern of transforms under the parent transform

        :param args: data about the parent transform
        :type args: list
        """

        in_ = [self.numPointsText2, self.radiusText, self.angularText]

        for le in in_:
            self._handle_empty_line(le)

        args.extend([
            int(in_[0].text()),
            float(in_[1].text()),
            self.tanRotCheck.isChecked(),
            self.cwCheck.isChecked(),
            float(in_[2].text())
        ])

        pmc.create_circular_pattern(*args)

    def _create_scatter_pattern(self, args):
        """
        This function creates a scatter pattern of transforms under the parent transform

        :param args: data about the parent transform
        :type args: list
        """

        points = []
        for i in range(self.scatter_model.rowCount()):
            str_point_lst = list_string_to_list(
                str(self.scatter_model.item(i).text()))

            point = pm_msg.Point()
            for s in str_point_lst:
                point.point.append(float(s))

            points.append(point)

        args.append(points)
        pmc.create_scatter_pattern(*args)

    @staticmethod
    def _handle_empty_line(line_edit):
        """
        This function checks if a field has been left empty, and if so, assigns it its default value

        :param line_edit: The input field for parameters
        :type line_edit: QLineEdit
        """

        if line_edit.text():
            return

        line_edit.setText(line_edit.placeholderText())

    @staticmethod
    def check_required_field_empty(field):
        """
        This function checks if a required field has been left empty, and if so,
        highlights the field with a red triangle

        :param field: The input field to check
        :type field: QLineEdit
        :return: `True` if field is empty, otherwise `False`
        :rtype: bool
        """

        if len(field.text()) == 0:
            field.setStyleSheet("border: 2px solid red;")
            rospy.logwarn('Required fields cannot be empty')

            return True

        return False
Example #41
0
class TapeWidget(QWidget):
    def __init__(self, parent = None):
        super().__init__(parent)

        self._main_layout    = QVBoxLayout(self)
        self._search_box     = QLineEdit(self)
        self._button_layout  = QHBoxLayout()

        self._view = QTreeView()
        self._view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self._view.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self._view.setHeaderHidden(True)

        self._add_note_button = QPushButton(self)
        self._add_note_button.setText("New note")

        self._add_child_button = QPushButton(self)
        self._add_child_button.setText("New child")

        self._add_sibling_button = QPushButton(self)
        self._add_sibling_button.setText("New sibling")

        self._delete_note_button = QPushButton(self)
        self._delete_note_button.setText("Delete note")

        self._button_layout.addWidget(self._add_note_button)
        self._button_layout.addWidget(self._add_sibling_button)
        self._button_layout.addWidget(self._add_child_button)
        self._button_layout.addWidget(self._delete_note_button)
        self._button_layout.addStretch()

        self._main_layout.addWidget(self._search_box)
        self._main_layout.addLayout(self._button_layout)
        self._main_layout.addWidget(self._view)

        self._tape_filter_proxy_model = TapeFilterProxyModel()
        self._note_delegate           = NoteDelegate()

        self._tape_model = QStandardItemModel()
        self.set_model(self._tape_model)
        self._view.setItemDelegate(self._note_delegate)
        self._view.setModel(self._tape_filter_proxy_model)

        self._add_note_button.clicked.connect(lambda checked: self.add_and_focus_note())
        self._add_sibling_button.clicked.connect(self._new_sibling_handler)
        self._add_child_button.clicked.connect(self._new_child_handler)
        self._delete_note_button.clicked.connect(self.delete_selected_notes)
        self._search_box.textChanged.connect(self._tape_filter_proxy_model.setFilterFixedString)

    def model(self):
        """ Returns the model that contains all notes managed by the tape.

            The model should be treated as read-only. You can only modify it indirectly through
            the methods provided by TapeWidget. """

        return self._tape_model

    def proxy_model(self):
        """ Returns the model that contains notes matching current filter.

            The model should be treated as read-only. You can only modify it indirectly through
            the methods provided by TapeWidget. """

        return self._tape_filter_proxy_model

    def set_model(self, model):
        assert (
            len(set([item_to_id(item) for item in all_items(model) if item_to_id(item) != None])) ==
            len(    [item_to_id(item) for item in all_items(model) if item_to_id(item) != None])
        )

        # NOTE: If there's an exception in setSourceModel(), we can hope that the source model
        # remains unchanged. That's why we assing to _tape_model only if that instruction succeeds.
        self._tape_filter_proxy_model.setSourceModel(model)
        self._tape_model = model

    def notes(self):
        return all_notes(self._tape_model)

    def assign_ids(self):
        assign_note_ids(self._tape_model)

    def create_empty_note(self):
        return Note(
            body       = "",
            tags       = [],
            created_at = datetime.utcnow()
        )

    def add_note(self, note = None, parent_index = None):
        # NOTE: Remember to use indexes from _tape_model, not _tape_filter_proxy_model here
        assert parent_index == None or self._tape_model.itemFromIndex(parent_index) != None and parent_index.isValid()

        root_item = self._tape_model.invisibleRootItem()
        if parent_index == None:
            parent_item = root_item
        else:
            parent_item = self._tape_model.itemFromIndex(parent_index)

        if note != None:
            assert note not in self.notes()
        else:
            note = self.create_empty_note()

        item = QStandardItem()
        set_item_note(item, note)
        parent_item.appendRow(item)

    def add_and_focus_note(self, parent_proxy_index = None):
        if parent_proxy_index != None:
            parent_index = self._tape_filter_proxy_model.mapToSource(parent_proxy_index)
        else:
            parent_index = None

        self.add_note(parent_index = parent_index)

        if parent_proxy_index != None:
            self._view.expand(parent_proxy_index)

            parent_item = self._tape_model.itemFromIndex(parent_index)
        else:
            parent_item = self._tape_model.invisibleRootItem()

        # NOTE: It's likely that the new note does not match the filter and won't not be present
        # in the proxy model. We want to select it and focus on it so the filter must be cleared.
        # And it must be cleared before taking the index in the proxy because changing the filter
        # may change the set of notes present in the proxy and invalidate the index.
        self.set_filter('')

        new_note_index       = parent_item.child(parent_item.rowCount() - 1).index()
        new_note_proxy_index = self._tape_filter_proxy_model.mapFromSource(new_note_index)

        self.clear_selection()
        self.set_note_selection(new_note_proxy_index, True)
        self._view.scrollTo(new_note_proxy_index)

    def remove_notes(self, indexes):
        remove_items(self._tape_model, indexes)

    def clear(self):
        self._tape_model.clear()

    def set_filter(self, text):
        # NOTE: This triggers textChanged() signal which applies the filter
        self._search_box.setText(text)

    def get_filter(self):
        return self._search_box.text()

    def selected_proxy_indexes(self):
        return self._view.selectedIndexes()

    def selected_indexes(self):
        return [self._tape_filter_proxy_model.mapToSource(proxy_index) for proxy_index in self.selected_proxy_indexes()]

    def set_note_selection(self, proxy_index, select):
        assert proxy_index != None and proxy_index.isValid()
        assert self._tape_model.itemFromIndex(self._tape_filter_proxy_model.mapToSource(proxy_index)) != None

        self._view.selectionModel().select(
            QItemSelection(proxy_index, proxy_index),
            QItemSelectionModel.Select if select else QItemSelectionModel.Deselect
        )

    def clear_selection(self):
        self._view.selectionModel().clear()

    def delete_selected_notes(self):
        self.remove_notes(self.selected_indexes())

    def _new_sibling_handler(self):
        selected_proxy_indexes = self._view.selectedIndexes()
        if len(selected_proxy_indexes) > 1:
            self.clear_selection()
            selected_proxy_indexes = []

        if len(selected_proxy_indexes) == 0 or selected_proxy_indexes[0].parent() == QModelIndex():
            self.add_and_focus_note()
        else:
            self.add_and_focus_note(selected_proxy_indexes[0].parent())

    def add_child_to_selected_element(self):
        selected_proxy_indexes = self._view.selectedIndexes()
        if len(selected_proxy_indexes) != 1:
            return False
        else:
            self.add_and_focus_note(selected_proxy_indexes[0])
            return True

    def _new_child_handler(self):
        added = self.add_child_to_selected_element()
        if not added:
            QMessageBox.warning(self, "Can't add note", "To be able to add a new child note select exactly one parent")