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());
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)
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)) ])
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)
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
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)
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])
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)
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)
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())
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
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())
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)
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()
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))
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)
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))
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())
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()
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)
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
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状态
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)
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)
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_()
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)
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() )
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)
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)
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))
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)
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])
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)
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 """
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))
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
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")