diff --git a/huffman_py/gui/InputDialog.py b/huffman_py/gui/InputDialog.py new file mode 100644 index 0000000..aead471 --- /dev/null +++ b/huffman_py/gui/InputDialog.py @@ -0,0 +1,48 @@ +# +# Sam Hadow - Huffman-py +# Copyright (C) 2023 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import * +class InputDialog(QDialog): + def __init__(self, texte1, texte2, thirdBox = False, texte3=None, parent=None): + super().__init__(parent) + + self.setWindowTitle("données") + + self.box1 = QLineEdit(self) + self.box2 = QLineEdit(self) + buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self); + + layout = QFormLayout(self) + layout.addRow(texte1, self.box1) + layout.addRow(texte2, self.box2) + + self.thirdBox = thirdBox + if self.thirdBox: + self.box3 = QLineEdit(self) + layout.addRow(texte3, self.box3) + + layout.addWidget(buttonBox) + + buttonBox.accepted.connect(self.accept) + buttonBox.rejected.connect(self.reject) + + def getInputs(self): + if self.thirdBox: + return (self.box1.text(), self.box2.text(), self.box3.text()) + else: + return (self.box1.text(), self.box2.text()) diff --git a/huffman_py/gui/Ui_MainWindow.py b/huffman_py/gui/Ui_MainWindow.py new file mode 100644 index 0000000..88051d2 --- /dev/null +++ b/huffman_py/gui/Ui_MainWindow.py @@ -0,0 +1,270 @@ +# +# Sam Hadow - Huffman-py +# Copyright (C) 2023 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from PyQt5 import QtCore, QtGui, QtWidgets, QtSvg +from PyQt5.QtWidgets import * +from PyQt5.QtGui import * +from PyQt5.QtCore import * + +import os, json, sys + +from huffman_py.gui.generate_graph import * +from huffman_py.fonctions.decode import * +from huffman_py.fonctions.encode import * +from huffman_py.gui.InputDialog import * +from huffman_py.fonctions.ifFileGetContent import * +from huffman_py.gui.messageBox import * + + + +class Ui_MainWindow(QWidget): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.resize(800, 800) + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.gridLayout = QGridLayout(self.centralwidget) + self.gridLayout.setObjectName(u"gridLayout") + + # widget pour attacher le graphe/texte + self.widget = QWidget(self.centralwidget) + self.widget.setObjectName(u"widget") + self.widget.setMinimumSize(QSize(10, 10)) + + self.gridLayout.addWidget(self.widget, 1, 0, 1, 1) + + # widget_2 pour attacher les boutons + self.widget_2 = QWidget(self.centralwidget) + self.widget_2.setObjectName(u"widget_2") + self.widget_2.setMaximumSize(QSize(4000, 50)) + + self.gridLayout.addWidget(self.widget_2, 0, 0, 1, 1) + + ## + MainWindow.setCentralWidget(self.centralwidget) + + self.retranslateUi(MainWindow) + + + # grille pour les widget texte ou svg + self.gridLayout_main = QGridLayout(self.widget) + self.gridLayout_main.setObjectName(u"gridLayout_main") + + # les widgets en question + ##### + # graphe de l'arbre + self.viewer = QtSvg.QSvgWidget() + self.gridLayout_main.addWidget(self.viewer,0, 0, 1, 1) + + ##### + # texte + self.texte = QTextEdit() + self.texte.setReadOnly(True) + self.gridLayout_main.addWidget(self.texte,1, 0, 1, 1) + + # on cache pour le moment les widget, on les affichera quand on en aura besoin + self.viewer.setVisible(False) + self.texte.setVisible(False) + + + # éléments de grid 2 + # boutons + ###################### + self.gridLayout_2 = QGridLayout(self.widget_2) + self.gridLayout_2.setObjectName(u"gridLayout_2") + + ##### + # push button encodage + self.pushButton_encode = QPushButton(self.widget_2) + self.pushButton_encode.setObjectName(u"pushButton") + + self.gridLayout_2.addWidget(self.pushButton_encode, 1, 0, 1, 1) + + self.pushButton_encode.setText(QCoreApplication.translate("MainWindow", u"encoder", None)) + + # fonction du bouton + self.pushButton_encode.clicked.connect(self.showDialogEncoder) + + + ##### + # push button décodage + self.pushButton_decode = QPushButton(self.widget_2) + self.pushButton_decode.setObjectName(u"pushButton_decode") + + self.gridLayout_2.addWidget(self.pushButton_decode, 1, 1, 1, 1) + + self.pushButton_decode.setText(QCoreApplication.translate("MainWindow", u"décoder", None)) + + # fonction du bouton + self.pushButton_decode.clicked.connect(self.showDialogDecoder) + + + ##### + # push button encode to file + self.pushButton_encodeToFile = QPushButton(self.widget_2) + self.pushButton_encodeToFile.setObjectName(u"pushButton_encodeToFile") + + self.gridLayout_2.addWidget(self.pushButton_encodeToFile, 1, 2, 1, 1) + + self.pushButton_encodeToFile.setText(QCoreApplication.translate("MainWindow", u"encoder vers fichier", None)) + + # fonction du bouton + self.pushButton_encodeToFile.clicked.connect(self.showDialogEncoderFichier) + + + ##### + #push button decode to file + self.pushButton_decodeToFile = QPushButton(self.widget_2) + self.pushButton_decodeToFile.setObjectName(u"pushButton_decodeToFile") + + self.gridLayout_2.addWidget(self.pushButton_decodeToFile, 1, 3, 1, 1) + + self.pushButton_decodeToFile.setText(QCoreApplication.translate("MainWindow", u"décoder vers fichier", None)) + + # fonction du bouton + self.pushButton_decodeToFile.clicked.connect(self.showDialogDecoderFichier) + + ##### fin boutons + + + # lancement d'une fonction avant la fermeture + qApp.aboutToQuit.connect(self.closeEvent) + + + ###### + # fonctions des boutons + def showDialogEncoder(self): + '''affiche le dialogue pour encoder un texte (chemin vers fichier accepté) et affiche l'arbre correspondant''' + text, ok = QInputDialog.getText(self, 'données', 'entrez le texte') + text = ifFileGetContent(text) + if ok and len(text) > 0: + the_data = (str(text)) + + print(the_data) + encoding, racine, _ = huffman_encode(the_data) + print("Encoded output", encoding) + print("Decoded Output", huffman_decode(encoding, racine)) + + #générer l'arbre à afficher (le .svg) + generate_graph(racine) + + # rafraichir l'affichage de l'arbre + self.viewer.load('tree.svg') + self.viewer.update() + + # on affiche le widget contenant l'arbre (et on affiche le texte encodé, dans un petit espace de préférence) + self.viewer.setVisible(True) + self.texte.setText(encoding) + self.texte.setMaximumSize(QSize(4000, 50)) + self.texte.setVisible(True) + + + def showDialogDecoder(self): + '''affiche le dialogue pour décoder un texte encodé avec ses codes (chemins vers fichiers acceptés)''' + dialog = InputDialog("binaire","Dict {lettre:code} JSON-formatted") + if dialog.exec(): + data = dialog.getInputs() + binaire = ifFileGetContent(data[0]) + + if len(binaire)>0 and len(data[1])>0: + try: + dico = json.loads(ifFileGetContent(data[1])) + except ValueError: + create_msg_box("Impossible de lire le dictionnaire fourni, est-ce bien un dictionnaire python formatté en JSON?","Dictionnaire invalide") + return 0 + + try: + text = decode_from_dico(binaire, dico) + except ValueError: + create_msg_box("Impossible de décoder le texte, est-ce bien le dictionnaire correspondant à ce texte encodé?","Erreur décodage") + return 0 + except TypeError: + create_msg_box("Impossible d'utiliser le texte fourni, est-ce bien un texte encodé en binaire?","Erreur entrée") + return 0 + + # on remplace le contenu du widget texte par le texte obtenu en décodant + self.texte.setText(text) + self.texte.setMaximumSize(QSize(4000, 4000)) + + # on affiche le widget contenant le texte (et on cache l'arbre) + self.viewer.setVisible(False) + self.texte.setVisible(True) + + def showDialogEncoderFichier(self): + '''affiche le dialogue pour encoder un texte (chemin vers fichier accepté) et crée 2 fichiers au chemin spécifié (un raw avec le texte encode et un json avec le dictionnaire des codes)''' + dialog = InputDialog("texte","chemin vers fichier") + if dialog.exec(): + data = dialog.getInputs() + texte = ifFileGetContent(data[0]) + if len(data[1])>0 and len(texte)>0: + try: + with open(data[1]+'.raw','w') as fd_bin, open(data[1]+'.json','w') as fd_dic: + #encoding, _, dico = huffman_encode(data[0]) + encoding, _, dico = huffman_encode(texte) + json.dump(dico,fd_dic) + fd_bin.write(encoding) + except FileNotFoundError: + create_msg_box("Le chemin est invalide, est-ce que le dossier dans lequel créer le fichier existe?","Erreur entrée") + + def showDialogDecoderFichier(self): + '''affiche le dialogue pour décoder un texte encodé avec ses codes (chemins vers fichiers acceptés), écrit le résultat dans le fichier donné en argument''' + dialog = InputDialog("texte","Dict {lettre:code} JSON-formatted", texte3 = "chemin vers fichier", thirdBox = True) + if dialog.exec(): + data = dialog.getInputs() + binaire = ifFileGetContent(data[0]) + + if len(binaire)>0 and len(data[1])>0: + try: + dico = json.loads(ifFileGetContent(data[1])) + except ValueError: + create_msg_box("Impossible de lire le dictionnaire fourni, est-ce bien un dictionnaire python formatté en JSON?","Dictionnaire invalide") + return 0 + + try: + texte = decode_from_dico(binaire, dico) + except ValueError: + create_msg_box("Impossible de décoder le texte, est-ce bien le dictionnaire correspondant à ce texte encodé?","Erreur décodage") + return 0 + except TypeError: + create_msg_box("Impossible d'utiliser le texte fourni, est-ce bien un texte encodé en binaire?","Erreur entrée") + return 0 + try: + with open(data[2],'w') as fd: + fd.write(texte) + except FileNotFoundError: + create_msg_box("Le chemin est invalide, est-ce que le dossier dans lequel créer le fichier existe?","Erreur entrée") + + ##### fin fonctions des boutons + + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("Huffman.py", u"Huffman.py", None)) + + def closeEvent(self): + # supprimer le fichier du graphe avant de fermer le programme + try: + os.unlink('./tree.gv') + os.unlink('./tree.svg') + except: + # si l'utilisateur avait ouvert le programme mais pas généré d'arbre + pass + print('exit') + sys.exit(0) + + + diff --git a/huffman_py/gui/generate_graph.py b/huffman_py/gui/generate_graph.py new file mode 100644 index 0000000..6fb9ebe --- /dev/null +++ b/huffman_py/gui/generate_graph.py @@ -0,0 +1,25 @@ +# +# Sam Hadow - Huffman-py +# Copyright (C) 2023 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from huffman_py.gui.generate_node import * +#le fichier generate_node.py + +def generate_graph(racine): + G = graphviz.Digraph(format='svg') + generate_node(G, racine) + G.render(outfile="tree.svg",overwrite_source=True) + #on peut ensuite intégrer l'arbre dans QT diff --git a/huffman_py/gui/generate_node.py b/huffman_py/gui/generate_node.py new file mode 100644 index 0000000..0c52ef7 --- /dev/null +++ b/huffman_py/gui/generate_node.py @@ -0,0 +1,36 @@ +# +# Sam Hadow - Huffman-py +# Copyright (C) 2023 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +import graphviz + +def generate_node(graph, sommet, father = None): + if sommet.left and sommet.right: + #pas une feuille + graph.node(str(sommet.identifiant), label = str(sommet.occurrence) , style = "filled", fillcolor = "red", shape="circle") + else: + #une feuille + graph.node(str(sommet.identifiant), label = graphviz.escape(str(sommet.occurrence)+'\n'+str(sommet.lettres)), style = "filled", fillcolor = "green") + # https://graphviz.readthedocs.io/en/stable/api.html#graphviz.escape + + if father != None: + graph.edge(str(father), str(sommet.identifiant), label=str(sommet.code)) + + if sommet.left: + generate_node(graph, sommet.left, sommet.identifiant) + if sommet.right: + generate_node(graph, sommet.right, sommet.identifiant) + diff --git a/huffman_py/gui/messageBox.py b/huffman_py/gui/messageBox.py new file mode 100644 index 0000000..f8348bd --- /dev/null +++ b/huffman_py/gui/messageBox.py @@ -0,0 +1,27 @@ +# +# Sam Hadow - Huffman-py +# Copyright (C) 2023 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import * + +def create_msg_box(message,titre): + msgBox = QMessageBox() + msgBox.setIcon(QMessageBox.Information) + msgBox.setText(message) + msgBox.setWindowTitle(titre) + msgBox.setStandardButtons(QMessageBox.Ok) + msgBox.exec_()