class CustomWidget(QtGui.QMainWindow): def __init__(self): """ Constructor """ QtGui.QMainWindow.__init__(self) self.name = "Custom widget" self.central_widget = QtGui.QWidget() self.setCentralWidget(self.central_widget) # TODO: This is ugly, improve it self.iconp = JConfig().icons_path self._createLayout() def _createGui(self): """ Subclasses must override this depending on the elements they want to add self._createOutputTree(), self._createOutputTable(), self._createOutputWindow() and add them to the corresponding layouts. """ raise NotImplementedError def _createToolBar(self, name): """ Subclasses need to define the specific Actions """ self.toolbar = self.addToolBar(name) self.toolbar.setMovable(False) def _createLayout(self): """ This creates the basic layout: Buttons & Outputs """ # Layouts (This is a common disposition) main_layout = QtGui.QVBoxLayout() self.button_layout = QtGui.QHBoxLayout() output_layout = QtGui.QVBoxLayout() # You will need to create your buttons # and add them to your layout like this: # self.button_layout.addWidget(button_1) # Output Layout Inner (QSplitter) # Add as many widgets as you please # They will be ordered vertically and # be resizable by the user # self.splitter.addWidget(self.table_label) # self.splitter.addWidget(...) self.splitter = QSplitter(QtCore.Qt.Vertical) # Nested layouts main_layout.addLayout(self.button_layout) output_layout.addWidget(self.splitter) main_layout.addLayout(output_layout) self.central_widget.setLayout(main_layout) def _createOutputWindow(self): """ Some binary analysis commands will output to this. """ self.output_label = QtGui.QLabel('Output') self.output_window = QTextEdit() self.output_window.setFontPointSize(10) self.output_window.setReadOnly(True) # Save it for later use self.output_window.original_textcolor = self.output_window.textColor() def _createOutputTable(self): """ A vanilla QTableWidget. Callbacks modify its properties, like number of columns, etc. """ self.table_label = QtGui.QLabel('Table Output') self.table = QTableWidget() self.table.setColumnCount(3) self.table.setColumnWidth(0, 100) self.table.setColumnWidth(1, 300) self.table.setColumnWidth(2, 300) self.table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) # Connect signals to slots self.table.customContextMenuRequested.connect(self._tablePopup) self.table.horizontalHeader().sectionDoubleClicked.connect( self._tableHeaderDoubleClicked) self.table.cellDoubleClicked.connect(self._tableCellDoubleClicked) def _createOutputTree(self): """ A QtreeWidget. Initially used to display those pesky dword comparison to immediate values. """ self.tree_label = QtGui.QLabel('Tree Output') self.tree = QTreeWidget() self.tree.setColumnCount(3) self.tree.setColumnWidth(0, 150) self.tree.setColumnWidth(1, 150) self.tree.setColumnWidth(2, 50) # Connect signals to slots self.tree.itemDoubleClicked.connect(self._treeElementDoubleClicked) ################################################################# # GUI Callbacks ################################################################# def _console_output(self, s="", err=False): """ Convenience wrapper """ if err: # Error message err_color = QColor('red') self.output_window.setTextColor(err_color) self.output_window.append(s) # restore original color self.output_window.setTextColor( self.output_window.original_textcolor) else: self.output_window.append(s) def _tableCellDoubleClicked(self, row, col): """ Most of the info displayed in QTableWidgets represent addresses. Default action: try to jump to it. """ it = self.table.item(row, col).text() try: addr = int(it, 16) jump_to_address(addr) except ValueError: self._console_output("[!] That does not look like an address...", err=True) return def _tablePopup(self, pos): """ Popup menu activated clicking the secondary button on the table """ menu = QtGui.QMenu() # Add menu entries delRow = menu.addAction(QIcon(self.iconp + "close.png"), "Delete Row") selItem = menu.addAction(QIcon(self.iconp + "bookmark.png"), "Mark entry") menu.addSeparator() origFunc = menu.addAction(QIcon(self.iconp + "lightning.png"), "Select origin function") destFunc = menu.addAction(QIcon(self.iconp + "flag.png"), "Select destination function") # Get entry clicked action = menu.exec_(self.mapToGlobal(pos)) # Dispatch :) if action == delRow: self.table.removeRow(self.table.currentRow()) elif action == selItem: self.table.currentItem().setBackground(QtGui.QColor('red')) elif action == origFunc: try: addr = self.table.currentItem().text() InfoUI.function_orig_ea = int(addr, 16) except: self._console_output("This does not look like an address...", err=True) elif action == destFunc: try: addr = self.table.currentItem().text() InfoUI.function_dest_ea = int(addr, 16) except: self._console_output("This does not look like an address...", err=True) def _tableHeaderDoubleClicked(self, index): """ Used to sort the contents """ self.table.sortItems(index, order=QtCore.Qt.AscendingOrder) def _treeElementDoubleClicked(self, item, column): """ QTreeWidgetElement callback. Basically it jumps to the selected address in IDA disassembly window. """ try: # Only interested in addresses addr_int = int(item.text(column), 16) jump_to_address(addr_int) # Paint some color item.setBackground(column, QtGui.QColor('green')) except: self._console_output("[!] That does not look like an address...", err=True)
class MainWindow(QMainWindow): def __init__(self): # QMainWindow.__init__(self) super().__init__() # use super() to avoid explicit dependency on the base class name # Note: must not pass the self reference to __init__ in this case! self.resize(800, 600) # Create the main content widget mainWidget = QWidget(self) self.setCentralWidget(mainWidget) # Create a text component at the top area of the main widget self.output = QTextEdit(mainWidget) self.output.setReadOnly(True) self.output.setLineWrapMode(QTextEdit.NoWrap); # set the font font = self.output.font() font.setFamily("Courier") font.setPointSize(10) self.output.setFont(font) # Set the background color # self.output.setTextBackgroundColor(Qt.red) # Only sets the background color for the text itself, not for the whole widget pal = self.output.palette() pal.setColor(QPalette.Base, Qt.black) self.output.setPalette(pal) mainLayout = QVBoxLayout(mainWidget) mainLayout.addWidget(self.output) # Create buttons in a grid layout below the top area buttonWidget = QWidget(mainWidget) self.buttonLayout = QGridLayout(buttonWidget) mainLayout.addWidget(buttonWidget) # Add some buttons to execute python code self.row = 0 self.column = 0 self.addButton("Clear console", lambda : self.output.clear()) self.newRow() # Add buttons for all the examples - attention: do not make "examples" # a local variable - otherwise, the Examples object would be destroyed # at the end of __init__ !!! self.examples = samplePackage.SampleModule.Examples(self) for example in self.examples.getExamples(): if example is None: self.newRow() else: self.addButton(example.label, example.function) # In a Python program, sys.excepthook is called just before the program exits. # So we can catch all fatal, uncaught exceptions and log them. # NOTE: we must be sure not to set the excepthook BEFORE we an actually # log something!! sys.excepthook = self.logException self.writelnColor(Qt.magenta, "Python version: {0}.{1}.{2} ({3})".format( sys.version_info[0], sys.version_info[1], sys.version_info[2], sys.version_info[3])) self.writelnColor(Qt.magenta, "Qt version : {0}".format(qVersion())) def logException(self, exctype, value, tb): self.writelnColor(Qt.red, ("\nFATAL ERROR: Uncaught exception\n" " {}: {}\n" "{}\n".format(exctype.__name__, value, ''.join(traceback.format_tb(tb)))) ) def addButton(self, label, function): theButton = QPushButton(label) theButton.clicked.connect(function) self.buttonLayout.addWidget(theButton, self.row, self.column) self.column += 1 def newRow(self): self.row += 1 self.column = 0 def writeColor(self, color, *text): theText = ' '.join(map(str, text)) self.output.setTextColor(color) # Note: append() adds a new paragraph! #self.output.append(theText) self.output.textCursor().movePosition(QTextCursor.End) self.output.insertPlainText(theText) # scroll console window to bottom sb = self.output.verticalScrollBar() sb.setValue(sb.maximum()) def write(self, *text): self.writeColor(Qt.green, *text) def writelnColor(self, color, *text): self.writeColor(color, *text) self.write('\n') def writeln(self, *text): self.writelnColor(Qt.green, *text)
class Ui_MainWindow(QMainWindow): """Cette classe contient tous les widgets de notre application.""" defaultPalette = QPalette() defaultPalette.setColor(QPalette.Base, QColor("#151515")) defaultPalette.setColor(QPalette.Text, Qt.white) def __init__(self): super(Ui_MainWindow, self).__init__() # initialise la GUI avec un exemple self.text = "Ceci est un petit texte d'exemple." # les variables sont en place, initialise la GUI self.initUI() # exécute l'exemple self.orgText.setText(self.text) self.encode_text(False) self.logLab.setText( u"Saisir du texte ou importer un fichier, puis pousser \n" u"le bouton correspondant à l'opération souhaitée.") def initUI(self): """Met en place les éléments de l'interface.""" # -+++++++------------------- main window -------------------+++++++- # self.setWindowTitle(u"Encodage / Décodage de Huffman") self.centerAndResize() centralwidget = QWidget(self) mainGrid = QGridLayout(centralwidget) mainGrid.setColumnMinimumWidth(0, 450) # -+++++++------------------ groupe analyse -----------------+++++++- # analysGroup = QGroupBox(u"Analyse", centralwidget) self.analysGrid = QGridLayout(analysGroup) # ----------- groupe de la table des codes ---------- # codeTableGroup = QGroupBox(u"Table des codes", analysGroup) codeTableGrid = QGridLayout(codeTableGroup) # un tableau pour les codes self.codesTableModel = MyTableModel() self.codesTable = QTableView(codeTableGroup) self.codesTable.setModel(self.codesTableModel) self.codesTable.setFont(QFont("Mono", 8)) self.codesTable.resizeColumnsToContents() self.codesTable.setSortingEnabled(True) codeTableGrid.addWidget(self.codesTable, 0, 0, 1, 1) self.analysGrid.addWidget(codeTableGroup, 1, 0, 1, 1) # ----------- label du ratio de compression ---------- # self.ratioLab = QLabel(u"Ratio de compression: ", analysGroup) font = QFont() font.setBold(True) font.setWeight(75) font.setKerning(True) self.ratioLab.setFont(font) self.analysGrid.addWidget(self.ratioLab, 2, 0, 1, 1) # -+++++++-------- groupe de la table de comparaison --------+++++++- # self.compGroup = QGroupBox(analysGroup) self.compGroup.setTitle(u"Comparaisons") compGrid = QGridLayout(self.compGroup) # un tableau pour le ratio self.compTable = QTableWidget(self.compGroup) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.compTable.sizePolicy().hasHeightForWidth()) self.compTable.setSizePolicy(sizePolicy) self.compTable.setBaseSize(QSize(0, 0)) font = QFont() font.setWeight(50) self.compTable.setFont(font) self.compTable.setEditTriggers(QAbstractItemView.NoEditTriggers) self.compTable.setShowGrid(True) self.compTable.setGridStyle(Qt.SolidLine) # lignes / colonnes self.compTable.setColumnCount(2) self.compTable.setRowCount(3) self.compTable.setVerticalHeaderItem(0, QTableWidgetItem("Taille (bits)")) self.compTable.setVerticalHeaderItem(1, QTableWidgetItem("Entropie")) self.compTable.setVerticalHeaderItem(2, QTableWidgetItem("Taille moy. (bits)")) for i in range(2): self.compTable.verticalHeaderItem(i).setTextAlignment( Qt.AlignRight) self.compTable.setHorizontalHeaderItem(0, QTableWidgetItem("ASCII")) self.compTable.setHorizontalHeaderItem(1, QTableWidgetItem("Huffman")) # nom des items self.compTabASCIIMem = QTableWidgetItem() self.compTable.setItem(0, 0, self.compTabASCIIMem) self.compTabASCIIEnt = QTableWidgetItem() self.compTable.setItem(1, 0, self.compTabASCIIEnt) self.compTabASCIIAvg = QTableWidgetItem() self.compTable.setItem(2, 0, self.compTabASCIIAvg) self.compTabHuffMem = QTableWidgetItem() self.compTable.setItem(0, 1, self.compTabHuffMem) self.compTabHuffEnt = QTableWidgetItem() self.compTable.setItem(1, 1, self.compTabHuffEnt) self.compTabHuffAvg = QTableWidgetItem() self.compTable.setItem(2, 1, self.compTabHuffAvg) # parem du tableau self.compTable.horizontalHeader().setCascadingSectionResizes(False) self.compTable.verticalHeader().setVisible(True) font = QFont("Mono", 8) self.compTable.setFont(font) compGrid.addWidget(self.compTable, 1, 0, 1, 1) self.analysGrid.addWidget(self.compGroup, 0, 0, 1, 1) mainGrid.addWidget(analysGroup, 0, 1, 1, 1) # -+++++++----------------- groupe du texte -----------------+++++++- # groupBox = QGroupBox(u"Texte", centralwidget) textGrid = QGridLayout(groupBox) # -+++++++------------- groupe du texte original ------------+++++++- # orgTextGroup = QGroupBox(u"Texte original (Ctrl+T)", groupBox) orgTextGrid = QGridLayout(orgTextGroup) self.orgText = QTextEdit(orgTextGroup) self.orgText.setPalette(self.defaultPalette) orgTextGrid.addWidget(self.orgText, 0, 0, 1, 1) textGrid.addWidget(orgTextGroup, 0, 0, 1, 2) # -+++++++------------ groupe du texte compressé ------------+++++++- # compressedTextGroup = QGroupBox(u"Texte compressé (Ctrl+H)", groupBox) compressedTextGrid = QGridLayout(compressedTextGroup) self.compressedText = QTextEdit(compressedTextGroup) self.compressedText.setPalette(self.defaultPalette) compressedTextGrid.addWidget(self.compressedText, 0, 0, 1, 1) textGrid.addWidget(compressedTextGroup, 1, 0, 1, 2) # -+++++++------------ groupe pour le texte ascii -----------+++++++- # asciiTextGroup = QGroupBox(u"Texte ASCII", groupBox) asciiTextGrid = QGridLayout(asciiTextGroup) self.asciiText = QTextBrowser(asciiTextGroup) self.asciiText.setPalette(self.defaultPalette) asciiTextGrid.addWidget(self.asciiText, 0, 0, 1, 1) textGrid.addWidget(asciiTextGroup, 2, 0, 1, 2) # -+++++++-------------------- label de log -----------------+++++++- # self.logLab = QLabel(analysGroup) textGrid.addWidget(self.logLab, 3, 0, 1, 2) # -+++++++----------- bouton pour encoder le texte ----------+++++++- # self.encodeBut = QPushButton(groupBox) self.encodeBut.setStatusTip( u"Cliquez sur ce bouton pour encoder le texte original.") self.encodeBut.setText(u"ENCODER") self.encodeBut.clicked.connect(self.encode_text) textGrid.addWidget(self.encodeBut, 4, 0, 1, 1) # -+++++++----------- bouton pour décoder le texte ----------+++++++- # self.decodeBut = QPushButton(groupBox) self.decodeBut.setStatusTip( u"Cliquez sur ce bouton pour décoder le texte compressé.") self.decodeBut.setText(u"DÉCODER") self.decodeBut.clicked.connect(self.decode_text) textGrid.addWidget(self.decodeBut, 4, 1, 1, 1) mainGrid.addWidget(groupBox, 0, 0, 1, 1) self.setCentralWidget(centralwidget) # -+++++++--------------- une barre de statut ---------------+++++++- # self.setStatusBar(QStatusBar(self)) # -+++++++--------------------- le menu ---------------------+++++++- # self.fileMenu = QMenu(u"Fichier") self.fileMenu.addAction(u"Importer un texte...", self.open_text) self.fileMenu.addAction( u"Importer un texte encodé...", lambda: self.open_text(True)) self.fileMenu.addAction(u"Importer un dictionnaire...", self.open_dict) self.fileMenu.addAction(u"Enregistrer le dictionnaire...", self.save_dict) self.fileMenu.addAction(u"Quitter", self.close) self.menuBar().addMenu(self.fileMenu) QMetaObject.connectSlotsByName(self) def open_text(self, compressed=False): """Ouvrir un fichier contenant un texte compressé ou non.""" fname, _ = QFileDialog.getOpenFileName(self, u'Ouvrir') f = open(fname, 'r') with f: data = f.read() if compressed: self.compressedText.setText(data) else: self.orgText.setText(data) def open_dict(self): """Ouvrir un dictionnaire de codes Huffman.""" fname, _ = QFileDialog.getOpenFileName(self, u'Ouvrir') self.occ = {} self.hCodes = {} self.aCodes = {} self.hCost = {} self.aCost = {} self.hCodes = create_dict_from_file(fname) self.update_codes_table() self.logLab.setText(u"Dictionnaire de Huffman importé.") return self.hCodes def save_dict(self): """Enregistrer le dictionnaire de codes Huffman.""" fname, _ = QFileDialog.getSaveFileName(self, "Enregistrer sous") save_dict_to_file(fname, self.hCodes) def make_tab_rows(self): """Génère le remplissage des lignes du tableau des codes.""" dictList = [self.occ, self.aCodes, self.hCodes, self.aCost, self.hCost] tabList = [] charList = self.hCodes.keys() if self.hCodes else self.occ.keys() for char in charList: l = ["'" + char + "'"] for dic in dictList: try: l.append(dic[char]) except KeyError: l.append('') tabList.append(l) return tabList def encode_text(self, wizard=True): """Encode le texte original.""" self.text = self.orgText.toPlainText().encode('utf-8') if not self.text: self.compressedText.setText(u"") self.asciiText.setText(u"") self.logLab.setText( u"<font color=#A52A2A>Rien à compresser.</font>") return self.occ = {} self.tree = () self.hCodes = {} self.aCodes = {} self.hCost = {} self.aCost = {} self.hSqueezed = [] self.aSqueezed = [] self.stringHSqueezed = '' self.stringASqueezed = '' if wizard: self.launch_wizard( EncodeWizard(self), u"<font color=#008000>Compression achevée.</font>") else: self.make_occ() self.make_tree() self.make_codes() self.make_cost() self.make_encoded_text() self.make_comp() def decode_text(self, wizard=True): """Décode le texte compressé.""" self.codeString = str( self.compressedText.toPlainText().replace(' ', '')) if not self.codeString or not self.hCodes: self.orgText.setText(u"") self.asciiText.setText(u"") if not self.codeString: self.logLab.setText( u"<font color=#A52A2A>Rien à décompresser.</font>") if not self.hCodes: self.logLab.setText( u"<font color=#A52A2A>Dictionnaire indisponible pour la décompression.</font>") return self.text = '' self.stringASqueezed = '' if wizard: self.launch_wizard( DecodeWizard(self), u"<font color=#008000>Texte décompressé.</font>") else: self.make_code_map() self.make_decoded_text() def launch_wizard(self, wizard, finishString): """Lance l'assistant d'en/décodage pour guider l'utilisateur. Cache les options inaccessibles pendant l'assistant. """ self.logLab.setText( u"<font color=#9E6A00>Opération en cours. " u"Suivre les indications.</font>") disItems = [self.orgText, self.compressedText, self.encodeBut, self.decodeBut, self.fileMenu.actions()[1], self.fileMenu.actions()[2], self.fileMenu.actions()[3]] for item in disItems: item.setEnabled(False) self.compGroup.setVisible(False) self.analysGrid.addWidget(wizard, 0, 0, 1, 1) res = wizard.exec_() if res: self.logLab.setText(finishString) else: self.logLab.setText( u"<font color=#A52A2A>Opération interrompue.</font>") for item in disItems: item.setEnabled(True) self.compGroup.setVisible(True) def update_ratio_lab(self): """Replace la valeur du label du ratio de compression.""" if not self.stringASqueezed: val = '/' else: val = (len(self.stringHSqueezed.replace(' ', '')) / float(len(self.stringASqueezed.replace(' ', '')))) self.ratioLab.setText(unicode('Taux de compression: ' + str(val))) def update_comp_table(self): """Met à jour le tableau de comparaison ASCII VS Huffman.""" self.compTabASCIIMem.setText(unicode(len(''.join(self.aSqueezed)))) self.compTabHuffMem.setText(unicode(len(''.join(self.hSqueezed)))) # entropie ? self.compTabASCIIEnt.setText('0') self.compTabHuffEnt.setText('0') self.compTabASCIIAvg.setText(unicode(8)) self.compTabHuffAvg.setText(unicode( average_code_length(self.hSqueezed))) self.compTable.resizeColumnsToContents() def update_codes_table(self, hlRow=[]): """Met à jour le tableau des codes et surligne les lignes de hlRow.""" self.codesTableModel.hlRow = hlRow self.codesTableModel.emptyTable() self.codesTableModel.fillTable(self.make_tab_rows()) self.codesTable.resizeColumnsToContents() def centerAndResize(self): """Centre et redimmensionne le widget.""" screen = QDesktopWidget().screenGeometry() self.resize(screen.width() / 1.6, screen.height() / 1.4) size = self.geometry() self.move( (screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) #===================== METHODS FOR EN/DECODE WIZARDS =====================# def make_encode_init(self): self.compressedText.setText(unicode(self.stringHSqueezed)) self.asciiText.setText(unicode(self.stringASqueezed)) self.orgText.setTextColor(QColor(Qt.darkGreen)) self.orgText.setText(unicode(self.text.decode('utf-8'))) self.update_codes_table() def make_occ(self): self.orgText.setTextColor(QColor(Qt.white)) self.orgText.setText(unicode(self.text.decode('utf-8'))) self.occ = occurences(self.text) self.update_codes_table([0, 1]) def make_tree(self): self.tree = make_trie(self.occ) self.update_codes_table() def make_codes(self): self.hCodes = make_codes(self.tree, {}) self.aCodes = make_ascii_codes(self.occ.keys(), {}) self.update_codes_table([2, 3]) def make_cost(self): self.hCost = tree_cost(self.hCodes, self.occ) self.aCost = tree_cost(self.aCodes, self.occ) self.update_codes_table([4, 5]) def make_encoded_text(self): self.hSqueezed = squeeze(self.text, self.hCodes) self.aSqueezed = squeeze(self.text, self.aCodes) self.stringHSqueezed = ' '.join(self.hSqueezed) self.stringASqueezed = ' '.join(self.aSqueezed) self.compressedText.setTextColor(QColor(Qt.darkGreen)) self.asciiText.setTextColor(QColor(Qt.darkYellow)) self.compressedText.setText(unicode(self.stringHSqueezed)) self.asciiText.setText(unicode(self.stringASqueezed)) self.update_codes_table() def make_comp(self): self.compressedText.setTextColor(QColor(Qt.white)) self.asciiText.setTextColor(QColor(Qt.white)) self.compressedText.setText(unicode(self.stringHSqueezed)) self.asciiText.setText(unicode(self.stringASqueezed)) self.update_codes_table() self.update_comp_table() self.update_ratio_lab() def make_decode_init(self): self.orgText.setText(unicode(self.text)) self.asciiText.setText(unicode(self.stringASqueezed)) self.compressedText.setTextColor(QColor(Qt.darkGreen)) self.compressedText.setText(self.compressedText.toPlainText()) def make_code_map(self): self.compressedText.setTextColor(QColor(Qt.white)) self.compressedText.setText(self.compressedText.toPlainText()) self.orgText.setText(unicode(self.text)) self.asciiText.setText(unicode(self.stringASqueezed)) self.codeMap = dict(zip(self.hCodes.values(), self.hCodes.keys())) self.update_codes_table([0, 3]) def make_decoded_text(self): self.unSqueezed = unsqueeze(self.codeString, self.codeMap) self.text = ''.join(self.unSqueezed) self.orgText.setTextColor(QColor(Qt.darkGreen)) self.orgText.setText(unicode(self.text.decode('utf-8'))) self.asciiText.setText(unicode(self.stringASqueezed)) self.update_codes_table() def make_ascii_decode(self): self.orgText.setTextColor(QColor(Qt.white)) self.orgText.setText(unicode(self.text.decode('utf-8'))) self.aCodes = make_ascii_codes(self.codeMap.values(), {}) self.aSqueezed = squeeze(self.text, self.aCodes) self.stringASqueezed = ' '.join(self.aSqueezed) self.occ = occurences(self.text) self.hCost = tree_cost(self.hCodes, self.occ) self.aCost = tree_cost(self.aCodes, self.occ) self.asciiText.setTextColor(QColor(Qt.darkGreen)) self.asciiText.setText(unicode(self.stringASqueezed)) self.update_codes_table([1, 2, 4, 5])
class QLogger(logging.Handler): '''Code from: https://stackoverflow.com/questions/28655198/best-way-to-display-logs-in-pyqt ''' def __init__(self, parent=None, format=settings.log_fmt, level=logging.INFO): logging.Handler.__init__(self) # Initialize a log handler as the super class self.setFormatter(logging.Formatter(format)) # Set the formatter for the logger self.setLevel(level) # Set the logging level self.frame = QFrame(parent) # Initialize a QFrame to place other widgets in self.frame2 = QFrame(parent) # Initialize frame2 for the label and checkbox self.label = QLabel('Logs') # Define a label for the frame self.check = QCheckBox('Debugging') # Checkbox to enable debugging logging self.check.clicked.connect(self.__changeLevel) # Connect checkbox clicked to the __changeLevel method self.log_widget = QTextEdit() # Initialize a QPlainTextWidget to write logs to self.log_widget.verticalScrollBar().minimum() # Set a vertical scroll bar on the log widget self.log_widget.horizontalScrollBar().minimum() # Set a horizontal scroll bar on the log widget self.log_widget.setLineWrapMode(self.log_widget.NoWrap) # Set line wrap mode to no wrapping self.log_widget.setFont(QFont("Courier", 12)) # Set the font to a monospaced font self.log_widget.setReadOnly(True) # Set log widget to read only layout = QHBoxLayout() # Initialize a horizontal layout scheme for the label and checkbox frame layout.addWidget(self.label) # Add the label to the layout scheme layout.addWidget(self.check) # Add the checkbox to the layout scheme self.frame2.setLayout(layout) # Set the layout for frame to the horizontal layout layout = QVBoxLayout() # Initialize a layout scheme for the widgets layout.addWidget(self.frame2) # Add the label/checkbox frame to the layout scheme layout.addWidget(self.log_widget) # Add the text widget to the layout scheme self.frame.setLayout(layout) # Set the layout of the fram to the layout scheme defined ############################################################################## def emit(self, record): ''' Overload the emit method so that it prints to the text widget ''' msg = self.format(record) # Format the message for logging if record.levelno >= logging.CRITICAL: # If the log level is critical self.log_widget.setTextColor(Qt.red) # Set text color to red elif record.levelno >= logging.ERROR: # Elif level is error self.log_widget.setTextColor(Qt.darkMagenta) # Set text color to darkMagenta elif record.levelno >= logging.WARNING: # Elif level is warning self.log_widget.setTextColor(Qt.darkCyan) # Set text color to darkCyan else: # Else self.log_widget.setTextColor(Qt.black) # Set text color to black self.log_widget.append(msg) # Add the log to the text widget ############################################################################## def write(self, m): ''' Overload the write method so that it does nothing ''' pass ############################################################################## def __changeLevel(self, *args): ''' Private method to change logging level ''' if self.check.isChecked(): self.setLevel(logging.DEBUG) # Get the Meso1819 root logger and add the handler to it else: self.setLevel(logging.INFO)
class CustomWidget(QtGui.QMainWindow): def __init__(self): """ Constructor """ QtGui.QMainWindow.__init__(self) self.name = "Custom widget" self.central_widget = QtGui.QWidget() self.setCentralWidget(self.central_widget) # TODO: This is ugly, improve it self.iconp = JConfig().icons_path self._createLayout() def _createGui(self): """ Subclasses must override this depending on the elements they want to add self._createOutputTree(), self._createOutputTable(), self._createOutputWindow() and add them to the corresponding layouts. """ raise NotImplementedError def _createToolBar(self, name): """ Subclasses need to define the specific Actions """ self.toolbar = self.addToolBar(name) self.toolbar.setMovable(False) def _createLayout(self): """ This creates the basic layout: Buttons & Outputs """ # Layouts (This is a common disposition) main_layout = QtGui.QVBoxLayout() self.button_layout = QtGui.QHBoxLayout() output_layout = QtGui.QVBoxLayout() # You will need to create your buttons # and add them to your layout like this: # self.button_layout.addWidget(button_1) # Output Layout Inner (QSplitter) # Add as many widgets as you please # They will be ordered vertically and # be resizable by the user # self.splitter.addWidget(self.table_label) # self.splitter.addWidget(...) self.splitter = QSplitter(QtCore.Qt.Vertical) # Nested layouts main_layout.addLayout(self.button_layout) output_layout.addWidget(self.splitter) main_layout.addLayout(output_layout) self.central_widget.setLayout(main_layout) def _createOutputWindow(self): """ Some binary analysis commands will output to this. """ self.output_label = QtGui.QLabel('Output') self.output_window = QTextEdit() self.output_window.setFontPointSize(10) self.output_window.setReadOnly(True) # Save it for later use self.output_window.original_textcolor = self.output_window.textColor() def _createOutputTable(self): """ A vanilla QTableWidget. Callbacks modify its properties, like number of columns, etc. """ self.table_label = QtGui.QLabel('Table Output') self.table = QTableWidget() self.table.setColumnCount(3) self.table.setColumnWidth(0, 100) self.table.setColumnWidth(1, 300) self.table.setColumnWidth(2, 300) self.table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) # Connect signals to slots self.table.customContextMenuRequested.connect(self._tablePopup) self.table.horizontalHeader().sectionDoubleClicked.connect(self._tableHeaderDoubleClicked) self.table.cellDoubleClicked.connect(self._tableCellDoubleClicked) def _createOutputTree(self): """ A QtreeWidget. Initially used to display those pesky dword comparison to immediate values. """ self.tree_label = QtGui.QLabel('Tree Output') self.tree = QTreeWidget() self.tree.setColumnCount(3) self.tree.setColumnWidth(0, 150) self.tree.setColumnWidth(1, 150) self.tree.setColumnWidth(2, 50) # Connect signals to slots self.tree.itemDoubleClicked.connect(self._treeElementDoubleClicked) ################################################################# # GUI Callbacks ################################################################# def _console_output(self, s = "", err = False): """ Convenience wrapper """ if err: # Error message err_color = QColor('red') self.output_window.setTextColor(err_color) self.output_window.append(s) # restore original color self.output_window.setTextColor(self.output_window.original_textcolor) else: self.output_window.append(s) def _tableCellDoubleClicked(self, row, col): """ Most of the info displayed in QTableWidgets represent addresses. Default action: try to jump to it. """ it = self.table.item(row, col).text() try: addr = int(it, 16) jump_to_address(addr) except ValueError: self._console_output("[!] That does not look like an address...", err = True) return def _tablePopup(self, pos): """ Popup menu activated clicking the secondary button on the table """ menu = QtGui.QMenu() # Add menu entries delRow = menu.addAction(QIcon(self.iconp + "close.png"), "Delete Row") selItem = menu.addAction(QIcon(self.iconp + "bookmark.png"), "Mark entry") menu.addSeparator() origFunc = menu.addAction(QIcon(self.iconp + "lightning.png"), "Select origin function") destFunc = menu.addAction(QIcon(self.iconp + "flag.png"), "Select destination function") # Get entry clicked action = menu.exec_(self.mapToGlobal(pos)) # Dispatch :) if action == delRow: self.table.removeRow(self.table.currentRow()) elif action == selItem: self.table.currentItem().setBackground(QtGui.QColor('red')) elif action == origFunc: try: addr = self.table.currentItem().text() InfoUI.function_orig_ea = int(addr, 16) except: self._console_output("This does not look like an address...", err = True) elif action == destFunc: try: addr = self.table.currentItem().text() InfoUI.function_dest_ea = int(addr, 16) except: self._console_output("This does not look like an address...", err = True) def _tableHeaderDoubleClicked(self, index): """ Used to sort the contents """ self.table.sortItems(index, order = QtCore.Qt.AscendingOrder) def _treeElementDoubleClicked(self, item, column): """ QTreeWidgetElement callback. Basically it jumps to the selected address in IDA disassembly window. """ try: # Only interested in addresses addr_int = int(item.text(column), 16) jump_to_address(addr_int) # Paint some color item.setBackground(column, QtGui.QColor('green')) except: self._console_output("[!] That does not look like an address...", err = True)