Course design of data structure and algorithm -- Huffman coding

1, Title

Huffman codec

2, Experimental purpose

  1. Master Huffman coding principle.
  2. Master the generation method of Huffman tree.
  3. Understand the implementation of data coding, compression and decoding output coding.

3, Demand analysis

  1. Initialization. Read the character set size n, n characters and N weights from the terminal, establish the Huffman tree, and store it in the file hfmTree.
  2. Encoding. Using the built Huffman tree (if it is not in memory, read it from the file hfmTree), encode the text in the file ToBeTran, and then store the results in the file CodeFile.
  3. Decoding. The code in the file CodeFile is decoded by using the built Huffman tree, and the results are stored in the file Textfile.
  4. Print the code file (print). The file CodeFile is displayed on the terminal in a compact format, with 50 codes per line. At the same time, the encoding file in the form of this character is written into the file CodePrin.
  5. Print Huffman Tree printing. Display the Huffman tree already in memory on the terminal in an intuitive way (such as tree), and write the Huffman tree in this character form into the file TreePrint.
  6. Network communication. Network communication is carried out in the form of Huffman coding as an encryption method.

4, Outline design

5, Program description

  1. Run with source file
    ① First install PyQt5 and graphviz libraries and https://graphviz.gitlab.io/download/ Install dot
    ② Then open the command line under the directory and enter the command: python Huffman codec py
    Note: python version should be greater than 3.8
  2. Use the packaged exe file
    Double click Huffman codec exe
    However, in both methods, the corresponding files and ui folders must be placed in the same directory

6, Detailed design

(1) Initialization

There are two forms of initialization: one is generated directly from the original text, and the other is based on the direct import of characters. On the basis of these two methods, you can also add, delete, modify and query the character set in the list, and save the file for the existing character set. Finally, when closing the window, the program will build a tree according to the existing character set

  1. Generated according to the original text
def add(self):
        # Add a blank line
        self.tableWidget.insertRow(self.tableWidget.rowCount())
    def generateCharacterSetFromRawtext(self):
        # Generate character set from original text
        self.tableWidget.clearContents()
        self.tableWidget.setRowCount(0)
        def getFrequency(text: str) -> dict:
            # Word frequency (Statistics)
            cnt = {}
            for i in text:
                if i not in cnt:
                    cnt[i] = 1
                else:
                    cnt[i] += 1
            return cnt
        CharacterSet = getFrequency(rawTextEdit.toPlainText())
        for i, j in CharacterSet.items():
            self.add()
            item1 = QTableWidgetItem(i)
            item2 = QTableWidgetItem(str(j))
            self.tableWidget.setItem(self.tableWidget.rowCount()-1, 0, item1)
            self.tableWidget.setItem(self.tableWidget.rowCount()-1, 1, item2)
  1. Import characters directly
    def importWordFrequency(self):
        # Import word frequency
        filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
        if ok:
            self.tableWidget.clearContents()
            self.tableWidget.setRowCount(0)
            with open(filePath, 'r', encoding='utf-8') as file:
                try:
                    frequency = file.read()
                except UnicodeDecodeError:
                    QMessageBox.critical(
                        self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.OK)
                    return
            global CharacterSet
            CharacterSet = {}
            textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', frequency)
            if len(textlines) == 0:
                QMessageBox.critical(self, "error", "Character set generation failed", QMessageBox.Ok)
                return
            for i, j, _ in textlines:
                try:
                    CharacterSet[i] = float(j)
                except ValueError:
                    QMessageBox.critical(
                        self, "error", "Character set generation failed", QMessageBox.Ok)
                    self.tableWidget.clearContents()
                    self.tableWidget.setRowCount(0)
                    CharacterSet = {}
                    return
                self.add()
                item1 = QTableWidgetItem(i)
                item2 = QTableWidgetItem(j)
                self.tableWidget.setItem(
                    self.tableWidget.rowCount()-1, 0, item1)
                self.tableWidget.setItem(
                    self.tableWidget.rowCount()-1, 1, item2)
  1. Additional additions, deletions and modifications
    def add(self):
        # Add a blank line
        self.tableWidget.insertRow(self.tableWidget.rowCount())

    def find(self):
        # Find characters or word frequencies or characters and word frequencies
        a: str = self.wordFrequencyEdit.text()
        b: str = self.frequencyEdit.text()
        i: int = 0
        if a and b:
            while i < self.tableWidget.rowCount():
                if self.tableWidget.item(i, 0).text() == a and self.tableWidget.item(i, 1).text() == b:
                    self.resultLabel.setText(str(i+1))
                    break
                i += 1
        elif not a and b:
            while i < self.tableWidget.rowCount():
                if self.tableWidget.item(i, 1).text() == b:
                    self.resultLabel.setText(str(i+1))
                    break
                i += 1
        elif a and not b:
            while i < self.tableWidget.rowCount():
                if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 0).text() == a:
                    self.resultLabel.setText(str(i+1))
                    break
                i += 1
        if i == self.tableWidget.rowCount():
            self.resultLabel.setText("not found")
  1. Save word frequency
    def saveWordFrequency(self):
        # Save file
        filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
        if ok:
            with open(filePath, 'w', encoding='utf-8') as file:
                for i in range(self.tableWidget.rowCount()):
                    m = '\t'.join([self.tableWidget.item(
                        i, 0).text(), self.tableWidget.item(i, 1).text()])
                    file.write(m+'\n')
  1. Build a tree
    def generateCharacterSetFromRawtext(self):
        # Generate character set from original text
        self.tableWidget.clearContents()
        self.tableWidget.setRowCount(0)
        def getFrequency(text: str) -> dict:
            # Word frequency (Statistics)
            cnt = {}
            for i in text:
                if i not in cnt:
                    cnt[i] = 1
                else:
                    cnt[i] += 1
            return cnt
        CharacterSet = getFrequency(rawTextEdit.toPlainText())
        for i, j in CharacterSet.items():
            self.add()
            item1 = QTableWidgetItem(i)
            item2 = QTableWidgetItem(str(j))
            self.tableWidget.setItem(self.tableWidget.rowCount()-1, 0, item1)
            self.tableWidget.setItem(self.tableWidget.rowCount()-1, 1, item2)

    def closeEvent(self, event):
        # Closing Windows 
        if self.tableWidget.rowCount() == 0:
            return
        global CharacterSet
        CharacterSet = {}
        # Save the character set in the table into the variable CharacterSet
        for i in range(self.tableWidget.rowCount()):
            if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 1):
                try:
                    CharacterSet[self.tableWidget.item(i, 0).text()] = float(
                        self.tableWidget.item(i, 1).text())
                except:
                    pass
        global HFTree
        # Update the tree based on the existing character set
        if CharacterSet != {}:
            HFTree = HuffmanTree(CharacterSet)
            global showSVGWidget
            if showSVGWidget:
                HFTree.printTree('tmp')
                showSVGWidget.update()
                paintTreeWindow.printInform()
(2) Encoding

Encoding encodes the original text according to the existing tree in memory, and there are two ways to read the original text, one is manual input, the other is to read the file, and the original text can also be saved

  1. code
    def encode(self, text: str) -> str:
        # Encode text in text
        p, q = '', ''  # p is the code of each character and q is the code of the whole article
        for i in text:
            for j in self.nodes:
                if i == j.name:
                    while j.parent:
                        if j.parent.lchild == j:
                            p += '0'
                        elif j.parent.rchild == j:
                            p += '1'
                        j = j.parent
                    q += p[::-1]
                    p = ''
                    break
            else:
                # If the current character is not in the character set, an empty ciphertext is returned
                return None
        return q
    def encoding(self):
        if not HFTree:
            QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok)
        elif rawTextEdit.toPlainText() == '':
            QMessageBox.critical(self, "error", "Please enter the original text", QMessageBox.Ok)
        else:
            t = HFTree.encode(rawTextEdit.toPlainText())
            if not t:
                QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
                return
            self.encodedTextEdit.setText(t)
  1. File read in
    def encodeFileReadin(self):
        filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
        if ok:
            with open(filePath, 'r', encoding='utf-8') as file:
                try:
                    text = file.read()
                except UnicodeDecodeError:
                    QMessageBox.critical(
                        self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok)
                    return
            self.rawTextEdit.setText(text)
  1. ③ Save original text
def saveRawTextContent(self):
    filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
    if ok:
        with open(filePath, 'w', encoding='utf-8') as file:
            file.write(self.rawTextEdit.toPlainText())
(3) Decoding

Decoding is to decode the ciphertext according to the existing tree in memory, and there are two ways to read the ciphertext, one is manual input, the other is to read the file, and the ciphertext can also be saved

  1. decoding
def decode(self, text: str) -> str:
    # Decode the 01 string in text in the tree
    root: TreeNode = self.rootnode
    result = ""
    for i in text:
        if i == '0':
            root = root.lchild
        elif i == '1':
            root = root.rchild
        elif i == '\n':  # '\ n' in compact format needs to be ignored
            continue
        else:
            return None
        if root.name:
            result += root.name
            root = self.rootnode
    if root != self.rootnode:
        return None
    else:
        return result
def decoding(self):
    if not HFTree:
        QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok)
    elif self.encodedTextEdit.toPlainText() == '':
        QMessageBox.critical(self, "error", "Please enter the ciphertext", QMessageBox.Ok)
    else:
        t = HFTree.decode(self.encodedTextEdit.toPlainText())
        if not t:
            QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
            return
        self.rawTextEdit.setText(t)
  1. File read in
    def decodeFileReadin(self):
        filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
        if ok:
            with open(filePath, 'r', encoding='utf-8') as file:
                try:
                    encodedTextEdit = file.read()
                except UnicodeDecodeError:
                    QMessageBox.critical(
                        self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok)
                    return
            if not checkDecodedText(encodedTextEdit):
                QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
                return
            self.encodedTextEdit.setText(encodedTextEdit)
③	Save ciphertext
    def saveEncodedTextContent(self):
        if not checkDecodedText(self.encodedTextEdit.toPlainText()):
            QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
            return
        filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
        if ok:
            with open(filePath, 'w', encoding='utf-8') as file:
                file.write(self.encodedTextEdit.toPlainText())
(4) Print code file (print)

The requirement here is to output in compact format and store files

  1. Compact format
def compactFormPrint(self):
    Text = self.encodedTextEdit.toPlainText()
    text = ''
    m = 50
    for i in Text.replace('\n', ''):
        text += i
        m -= 1
        if m == 0:
            text += '\n'
            m = 50
    self.encodedTextEdit.setPlainText(text)
  1. storage
def saveEncodedTextContent(self):
    if not checkDecodedText(self.encodedTextEdit.toPlainText()):
        QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
        return
    filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
    if ok:
        with open(filePath, 'w', encoding='utf-8') as file:
            file.write(self.encodedTextEdit.toPlainText())
(5) Print Huffman Tree printing

Print Huffman tree, including spanning tree information, image operation in the control, tree information display, tree import and storage, and related operations of viewing character set

  1. Picture of spanning tree
def printTree(self, filename=None):
    # Picture of spanning tree
    dot = Digraph(comment="Generated tree")
    dot.attr('node', fontname="STXinwei", shape='circle', fontsize="20")
    for i, j in enumerate(self.nodes):
        if j.name == '' or not j.name:
            dot.node(str(i), '')
        elif j.name == ' ':
            dot.node(str(i), '[ ]')  # Spaces are displayed as' [] '
        elif j.name == '\n':
            dot.node(str(i), '\\\\n')  # The newline character is shown as' \ n 'escape, where it will also be called, so four slashes are required
        elif j.name == '\t':
            dot.node(str(i), '\\\\t')  # The tab displays as' \ t '
        else:
            dot.node(str(i), j.name)
    dot.attr('graph', rankdir='LR')
    for i in self.nodes[::-1]:
        if not (i.rchild or i.lchild):
            break
        if i.lchild:
            dot.edge(str(self.nodes.index(i)), str(
                self.nodes.index(i.lchild)), '0', constraint='true')
        if i.rchild:
            dot.edge(str(self.nodes.index(i)), str(
                self.nodes.index(i.rchild)), '1', constraint='true')
    dot.render(filename, view=False, format='svg')
  1. Zoom in and out of tree image
class ShowSVGWidget(QWidget):
    # Custom control to display svg pictures
    leftClick: bool
    svgrender: QSvgRenderer
    defaultSize: QSizeF
    point: QPoint
    scale = 1

    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        # Construct a blank svg image
        self.svgrender = QSvgRenderer(
            b'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 0 0"  width="512pt" height="512pt"></svg>')
        # Get picture default size
        self.defaultSize = QSizeF(self.svgrender.defaultSize())
        self.point = QPoint(0, 0)
        self.scale = 1

    def update(self):
        # Update picture
        self.svgrender = QSvgRenderer("tmp.svg")
        self.defaultSize = QSizeF(self.svgrender.defaultSize())
        self.point = QPoint(0, 0)
        self.scale = 1
        self.repaint()

    def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
        # Painting event (callback function)
        painter = QPainter()  # paint brush
        painter.begin(self)
        self.svgrender.render(painter, QRectF(
            self.point, self.defaultSize*self.scale))  # svg renderer to paint, (brush, qrectf (position, size)) (F means float)
        painter.end()

    def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
        # Mouse movement event (callback function)
        if self.leftClick:
            self.endPos = a0.pos()-self.startPos
            self.point += self.endPos
            self.startPos = a0.pos()
            self.repaint()

    def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None:
        # Mouse click event (callback function)
        if a0.button() == Qt.LeftButton:
            self.leftClick = True
            self.startPos = a0.pos()

    def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None:
        # Mouse release event (callback function)
        if a0.button() == Qt.LeftButton:
            self.leftClick = False

    def wheelEvent(self, a0: QtGui.QWheelEvent) -> None:
        # Zoom the image according to the position of the cursor
        oldScale = self.scale
        if a0.angleDelta().y() > 0:
            # enlarge
            if self.scale <= 5.0:
                self.scale *= 1.1
        elif a0.angleDelta().y() < 0:
            # narrow
            if self.scale >= 0.2:
                self.scale *= 0.9
        self.point = a0.pos()-(self.scale/oldScale*(a0.pos()-self.point))
        self.repaint()
  1. Display of tree information
def printInform(self):
    # Update tree information
    self.treeHeightlabel.setText(str(self.TreeDepth(HFTree)))
    self.nodeCountlabel.setText(str(len(HFTree.characterset)*2-1))
    self.leafCountlabel.setText(str(len(HFTree.characterset)))
  1. Tree file reading
def importtree(self):
    # Import the tree information into the picture
    filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
    if ok:
        with open(filePath, 'r', encoding='utf-8') as file:
            try:
                text = file.read()
            except UnicodeDecodeError:
                QMessageBox.critical(
                    self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok)
                return
        global CharacterSet
        CharacterSet = self.CharacterSet
        textlines = re.findall(r'([\s\S])\t(\S+)\t\S+(\n|$)', text)
        # Reset the character set information after import and update the tree in memory
        for i, j, _ in textlines:
            CharacterSet[i] = float(j)
        global HFTree
        if CharacterSet != {}:
            HFTree = HuffmanTree(CharacterSet)
            global showSVGWidget
            HFTree.printTree('tmp')
            showSVGWidget.update()
            self.printInform()  # Write the tree information on the panel
  1. Tree storage
def savetree(self):
    # Save tree information
    filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
    if ok:
        with open(filePath, 'w', encoding='utf-8') as file:
            for i, j in HFTree.characterset.items():
                m = '\t'.join([i, str(j), HFTree.encode(i)])
                file.write(m+'\n')
(6) Network communication

Network communication includes server listening interface and waiting for connection, client establishing connection, tree and ciphertext transmission, waiting for reception and disconnection

  1. Server listening port
def buildServerConnection(self):
    # Server listening port
    try:
        # Get port number
        port = int(self.lineEdit.text())
    except ValueError:
        QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok)
        return
    # Create a socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        # Set the listening port and listen
        s.bind(("0.0.0.0", port))
        s.listen()
    except OSError:
        QMessageBox.critical(self, "error", "The port is already occupied", QMessageBox.Ok)
        return
    # Waiting for client connection
    self.stateLabel.setText("Waiting for connection")
    # Start a new thread to wait for the connection to prevent the program from blocking, and use the daemon flag to automatically end all threads with this flag when the main thread ends
    threading.Thread(target=self.handleClient,
                     args=[s], daemon=True).start()
  1. Server waiting for connection
def handleClient(self, s: socket.socket):
    # Server side waiting for connection
    c = s.accept()[0]
    self.stateLabel.setText("Connected")
    self.s = c
    # Start the thread waiting to receive
    threading.Thread(target=self.waitRecv, args=[c], daemon=True).start()
  1. Client establish connection
def buildClientConnection(self):
    # Client establish connection
    try:
        # Get IP address
        ip = self.connectIpEditText.text()
        if ip == None:
            QMessageBox.critical(
                self, "error", "There are currently no entered IP address", QMessageBox.Ok)
            return
        port = int(self.connectPortEditText.text())
    except ValueError:
        QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok)
        return
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect((ip, port))
    except ConnectionRefusedError:
        QMessageBox.critical(self, "error", "connection failed", QMessageBox.Ok)
        return
    except OSError:
        QMessageBox.critical(self, "error", "IP Or port error", QMessageBox.Ok)
        return
    self.stateLabel.setText("Connected")
    self.s = s
    # The connection is successful. Start the thread waiting to receive
    threading.Thread(target=self.waitRecv, args=[s], daemon=True).start()
  1. Tree and ciphertext transmission
def sendTree(self):
    # Send tree
    if not self.s:
        QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok)
        return
    global CharacterSet
    if not CharacterSet or not HFTree:
        QMessageBox.critical(self, "error", "The current tree is empty", QMessageBox.Ok)
        return
    content = 't'  # Flag of sending tree
    for i, j in CharacterSet.items():
        content += i+"\t"+str(j)+'\n'
    # Convert it to Byte for sending
    self.s.sendall(content.encode())
    QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok)

def sendText(self):
    # Send ciphertext
    if not self.s:
        QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok)
        return
    global encodedTextEdit
    content = encodedTextEdit.toPlainText()
    if not checkDecodedText(content):
        QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
        return
    self.s.sendall(('c'+content).encode())
    QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok)
  1. Waiting for connection
def waitRecv(self, s: socket.socket):
    # Waiting to accept thread
    try:
        while True:
            data = s.recv(10000000)
            # Convert content to str type
            data = data.decode()
            if data[0] == 't':
                data = data[1:]
                textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', data)
                global CharacterSet
                CharacterSet = {}
                for i, j, _ in textlines:
                    try:
                        CharacterSet[i] = float(j)
                    except ValueError:
                        self.stateLabel.setText("Useless data received")
                        self.tableWidget.clearContents()
                        self.tableWidget.setRowCount(0)
                        CharacterSet = {}
                        return
                global HFTree
                if CharacterSet != {}:
                    HFTree = HuffmanTree(CharacterSet)
                    self.stateLabel.setText("Tree received")
                else:
                    self.stateLabel.setText("Empty tree received")
            elif data[0] == 'c':
                self.stateLabel.setText("Ciphertext received")
                data = data[1:]
                self.setEncodedTextSign.emit(data)
            else:
                self.stateLabel.setText("Useless data received")
    except ConnectionResetError:  # The other party disconnected
        self.stateLabel.setText("Disconnected")
        self.s = None
    except ConnectionAbortedError:  # Disconnect yourself
        pass
  1. Disconnect
def breakConnection(self):
    # Disconnect button event
    try:
        self.s.close()
        self.s = None
        self.stateLabel.setText("Not connected")
    except:
        pass

The following is the source code:

from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUi
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtSvg import *
from typing import Dict, List
from graphviz import Digraph
import sys
import os
import socket
import re
import threading
import multiprocessing

class TreeNode:
    parent: 'TreeNode' = None  # Parent node
    lchild: 'TreeNode' = None  # Left node
    rchild: 'TreeNode' = None  # Right node
    weight: float = None  # Weight, i.e. word frequency
    name: str = None  # Name, i.e. character

    def __init__(self, name=None, weight=None) -> None:
        self.name = name
        self.weight = weight

class HuffmanTree:
    rootnode: TreeNode = None  # Root node
    nodes: List[TreeNode] = None  # List of nodes
    characterset: Dict[str, float] = None  # Character set dictionary

    def __init__(self, characterset: Dict[str, float]):
        if characterset == {} or characterset == None:  # If the character set is empty or there is no character set, return directly without constructing the tree
            return
        # To build a tree, first write it in a list
        nodes: List[TreeNode] = [TreeNode(key, value) for key, value in characterset.items()] +\
                                [TreeNode()
                                 for _ in range(len(characterset)-1)]
        for i in range(len(characterset), len(nodes)):
            x = self.select(i, nodes)
            nodes[x].parent = nodes[i]
            y = self.select(i, nodes)
            nodes[y].parent = nodes[i]
            nodes[i].lchild = nodes[x]
            nodes[i].rchild = nodes[y]
            nodes[i].weight = nodes[x].weight+nodes[y].weight
        self.rootnode = nodes[-1]
        self.nodes = nodes
        self.characterset = characterset

    def select(self, k: int, nodes: List[TreeNode]) -> int:
        # Find the node with the smallest weight and no parent node in the first k nodes, and return the minimum value
        x: int = None
        for i in range(k):
            if not nodes[i].parent:  # If you find the first node without a parent node, mark it with an x
                x = i
                break
        for j in range(x, k):  # After node x
            if nodes[j].weight < nodes[x].weight and not nodes[j].parent:
                x = j
                break
        return x

    def encode(self, text: str) -> str:
        # Encode text in text
        p, q = '', ''  # p is the code of each character and q is the code of the whole article
        for i in text:
            for j in self.nodes:
                if i == j.name:
                    while j.parent:
                        if j.parent.lchild == j:
                            p += '0'
                        elif j.parent.rchild == j:
                            p += '1'
                        j = j.parent
                    q += p[::-1]
                    p = ''
                    break
            else:
                # If the current character is not in the character set, an empty ciphertext is returned
                return None
        return q

    def decode(self, text: str) -> str:
        # Decode the 01 string in text in the tree
        root: TreeNode = self.rootnode
        result = ""
        for i in text:
            if i == '0':
                root = root.lchild
            elif i == '1':
                root = root.rchild
            elif i == '\n':  # '\ n' in compact format needs to be ignored
                continue
            else:
                return None
            if root.name:
                result += root.name
                root = self.rootnode
        if root != self.rootnode:
            return None
        else:
            return result

    def printTree(self, filename=None):
        # Picture of spanning tree
        dot = Digraph(comment="Generated tree")
        dot.attr('node', fontname="STXinwei", shape='circle', fontsize="20")
        for i, j in enumerate(self.nodes):
            if j.name == '' or not j.name:
                dot.node(str(i), '')
            elif j.name == ' ':
                dot.node(str(i), '[ ]')  # Spaces are displayed as' [] '
            elif j.name == '\n':
                dot.node(str(i), '\\\\n')  # The newline character is shown as' \ n 'escape, where it will also be called, so four slashes are required
            elif j.name == '\t':
                dot.node(str(i), '\\\\t')  # The tab displays as' \ t '
            else:
                dot.node(str(i), j.name)
        dot.attr('graph', rankdir='LR')
        for i in self.nodes[::-1]:
            if not (i.rchild or i.lchild):
                break
            if i.lchild:
                dot.edge(str(self.nodes.index(i)), str(
                    self.nodes.index(i.lchild)), '0', constraint='true')
            if i.rchild:
                dot.edge(str(self.nodes.index(i)), str(
                    self.nodes.index(i.rchild)), '1', constraint='true')
        
        dot.render(filename, view=False, format='svg', cleanup=True)

def checkDecodedText(text: str) -> bool:
    # Check the legality of ciphertext content
    for i in text:
        if i not in ['0', '1', '\n']:
            return False
    return True

rawTextEdit: QTextEdit = None  # Text box where the original text is located
encodedTextEdit: QTextEdit = None  # Text box of ciphertext
HFTree: HuffmanTree = None  # Huffman tree 
CharacterSet = {}  # character set
showSVGWidget: "ShowSVGWidget" = None  # Show svg controls
paintTreeWindow:"PaintTreeWindow"=None
charsetWindow:"CharsetWindow"=None

class MainWindow(QWidget):
    encodedTextEdit: QTextEdit

    def __init__(self):
        super().__init__()
        loadUi("ui/main.ui", self)  # Each control in the form is loaded
        self.setWindowIcon(QIcon("ui/icon.ico"))
        global rawTextEdit
        rawTextEdit = self.rawTextEdit
        global encodedTextEdit
        encodedTextEdit = self.encodedTextEdit
        self.rawSaveFileButton.clicked.connect(self.saveRawTextContent)
        self.encodedSaveFileButton.clicked.connect(self.saveEncodedTextContent)
        self.rawOpenFileButton.clicked.connect(self.encodeFileReadin)
        self.encodedOpenFileButton.clicked.connect(self.decodeFileReadin)
        self.pushButton.clicked.connect(lambda: QMessageBox.about(
            self, "About the author", "Name: Sun Lianqi\n School: Zhejiang University of Technology\n Date: 2021.6"))
        self.compactFormatButton.clicked.connect(self.compactFormPrint)
        self.encodedButton.clicked.connect(self.encoding)
        self.decodeButton.clicked.connect(self.decoding)

    def saveRawTextContent(self):
        filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
        if ok:
            with open(filePath, 'w', encoding='utf-8') as file:
                file.write(self.rawTextEdit.toPlainText())

    def saveEncodedTextContent(self):
        if not checkDecodedText(self.encodedTextEdit.toPlainText()):
            QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
            return
        filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
        if ok:
            with open(filePath, 'w', encoding='utf-8') as file:
                file.write(self.encodedTextEdit.toPlainText())

    def encodeFileReadin(self):
        filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
        if ok:
            with open(filePath, 'r', encoding='utf-8') as file:
                try:
                    text = file.read()
                except UnicodeDecodeError:
                    QMessageBox.critical(
                        self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok)
                    return
            self.rawTextEdit.setText(text)

    def decodeFileReadin(self):
        filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
        if ok:
            with open(filePath, 'r', encoding='utf-8') as file:
                try:
                    encodedTextEdit = file.read()
                except UnicodeDecodeError:
                    QMessageBox.critical(
                        self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok)
                    return
            if not checkDecodedText(encodedTextEdit):
                QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
                return
            self.encodedTextEdit.setText(encodedTextEdit)

    def compactFormPrint(self):
        Text = self.encodedTextEdit.toPlainText()
        text = ''
        m = 50
        for i in Text.replace('\n', ''):
            text += i
            m -= 1
            if m == 0:
                text += '\n'
                m = 50
        self.encodedTextEdit.setPlainText(text)

    def encoding(self):
        if not HFTree:
            QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok)
        elif rawTextEdit.toPlainText() == '':
            QMessageBox.critical(self, "error", "Please enter the original text", QMessageBox.Ok)
        else:
            t = HFTree.encode(rawTextEdit.toPlainText())
            if not t:
                QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
                return
            self.encodedTextEdit.setText(t)

    def decoding(self):
        if not HFTree:
            QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok)
        elif self.encodedTextEdit.toPlainText() == '':
            QMessageBox.critical(self, "error", "Please enter the ciphertext", QMessageBox.Ok)
        else:
            t = HFTree.decode(self.encodedTextEdit.toPlainText())
            if not t:
                QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
                return
            self.rawTextEdit.setText(t)

    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
        try:
            os.remove("tmp.svg")
        except FileNotFoundError:
            pass
        sys.exit()

class DoubleDelegate(QItemDelegate):
    # Limit the number of floating-point types to

    def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex) -> QWidget:
        edit = QLineEdit(parent)
        v = QDoubleValidator(parent)
        v.setBottom(0)
        edit.setValidator(v)
        return edit

class OneCharDelegate(QItemDelegate):
    # Limit input to a single character

    def createEditor(self, parent: QWidget, option: 'QStyleOptionViewItem', index: QModelIndex) -> QWidget:
        edit = QLineEdit(parent)
        v = QRegExpValidator(parent)
        v.setRegExp(QRegExp(r'.'))
        edit.setValidator(v)
        return edit

class CharsetWindow(QWidget):
    tableWidget: QTableWidget

    def __init__(self):
        super().__init__()
        loadUi("ui/charset.ui", self)
        self.tableWidget.setItemDelegateForColumn(0, OneCharDelegate(self))
        self.tableWidget.setItemDelegateForColumn(1, DoubleDelegate(self))
        self.setWindowIcon(QIcon("ui/icon.ico"))
        self.addButton.clicked.connect(self.add)
        self.findButton.clicked.connect(self.find)
        self.deleteButton.clicked.connect(
            lambda: self.tableWidget.removeRow(self.tableWidget.currentRow()))
        self.inputWordFrequencyButton.clicked.connect(self.importWordFrequency)
        self.saveButton.clicked.connect(self.saveWordFrequency)
        self.generateButton.clicked.connect(
            self.generateCharacterSetFromRawtext)
        # Limit the input of lineedit to one character
        self.wordFrequencyEdit.setValidator(
            QRegExpValidator(QRegExp(r'.'), self))
        d = QDoubleValidator(self)
        d.setBottom(0)
        self.frequencyEdit.setValidator(d)

    def showEvent(self, e):
        if HFTree:  # If there is a tree, the character set is generated from the existing tree
            self.tableWidget.clearContents()
            self.tableWidget.setRowCount(0)
            global CharacterSet
            for i, j in CharacterSet.items():
                self.add()
                item1 = QTableWidgetItem(i)
                item2 = QTableWidgetItem(str(j))
                self.tableWidget.setItem(
                    self.tableWidget.rowCount()-1, 0, item1)
                self.tableWidget.setItem(
                    self.tableWidget.rowCount()-1, 1, item2)

    def add(self):
        # Add a blank line
        self.tableWidget.insertRow(self.tableWidget.rowCount())

    def find(self):
        # Find characters or word frequencies or characters and word frequencies
        a: str = self.wordFrequencyEdit.text()
        b: str = self.frequencyEdit.text()
        i: int = 0
        if a and b:
            while i < self.tableWidget.rowCount():
                if self.tableWidget.item(i, 0).text() == a and self.tableWidget.item(i, 1).text() == b:
                    self.resultLabel.setText(str(i+1))
                    break
                i += 1
        elif not a and b:
            while i < self.tableWidget.rowCount():
                if self.tableWidget.item(i, 1).text() == b:
                    self.resultLabel.setText(str(i+1))
                    break
                i += 1
        elif a and not b:
            while i < self.tableWidget.rowCount():
                if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 0).text() == a:
                    self.resultLabel.setText(str(i+1))
                    break
                i += 1
        if i == self.tableWidget.rowCount():
            self.resultLabel.setText("not found")

    def importWordFrequency(self):
        # Import word frequency
        filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
        if ok:
            self.tableWidget.clearContents()
            self.tableWidget.setRowCount(0)
            with open(filePath, 'r', encoding='utf-8') as file:
                try:
                    frequency = file.read()
                except UnicodeDecodeError:
                    QMessageBox.critical(
                        self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.OK)
                    return
            global CharacterSet
            CharacterSet = {}
            textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', frequency)
            if len(textlines) == 0:
                QMessageBox.critical(self, "error", "Character set generation failed", QMessageBox.Ok)
                return
            for i, j, _ in textlines:
                try:
                    CharacterSet[i] = float(j)
                except ValueError:
                    QMessageBox.critical(
                        self, "error", "Character set generation failed", QMessageBox.Ok)
                    self.tableWidget.clearContents()
                    self.tableWidget.setRowCount(0)
                    CharacterSet = {}
                    return
                self.add()
                item1 = QTableWidgetItem(i)
                item2 = QTableWidgetItem(j)
                self.tableWidget.setItem(
                    self.tableWidget.rowCount()-1, 0, item1)
                self.tableWidget.setItem(
                    self.tableWidget.rowCount()-1, 1, item2)

    def saveWordFrequency(self):
        # Save file
        filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
        if ok:
            with open(filePath, 'w', encoding='utf-8') as file:
                for i in range(self.tableWidget.rowCount()):
                    m = '\t'.join([self.tableWidget.item(
                        i, 0).text(), self.tableWidget.item(i, 1).text()])
                    file.write(m+'\n')

    def generateCharacterSetFromRawtext(self):
        # Generate character set from original text
        self.tableWidget.clearContents()
        self.tableWidget.setRowCount(0)
        def getFrequency(text: str) -> dict:
            # Word frequency (Statistics)
            cnt = {}
            for i in text:
                if i not in cnt:
                    cnt[i] = 1
                else:
                    cnt[i] += 1
            return cnt
        CharacterSet = getFrequency(rawTextEdit.toPlainText())
        for i, j in CharacterSet.items():
            self.add()
            item1 = QTableWidgetItem(i)
            item2 = QTableWidgetItem(str(j))
            self.tableWidget.setItem(self.tableWidget.rowCount()-1, 0, item1)
            self.tableWidget.setItem(self.tableWidget.rowCount()-1, 1, item2)

    def closeEvent(self, event):
        # Closing Windows 
        if self.tableWidget.rowCount() == 0:
            return
        global CharacterSet
        CharacterSet = {}
        # Save the character set in the table into the variable CharacterSet
        for i in range(self.tableWidget.rowCount()):
            if self.tableWidget.item(i, 0) and self.tableWidget.item(i, 1):
                try:
                    CharacterSet[self.tableWidget.item(i, 0).text()] = float(
                        self.tableWidget.item(i, 1).text())
                except:
                    pass
        global HFTree
        # Update the tree based on the existing character set
        if CharacterSet != {}:
            HFTree = HuffmanTree(CharacterSet)
            global showSVGWidget
            if showSVGWidget:
                HFTree.printTree('tmp')
                showSVGWidget.update()
                paintTreeWindow.printInform()

class NetTransportWindow(QWidget):
    s: socket.socket = None
    lineEdit: QLineEdit  # Server port input box
    connectIpEditText: QLineEdit  # Client IP input box
    connectPortEditText: QLineEdit  # Client port input box
    setEncodedTextSign = pyqtSignal(str)  # Modified ciphertext box signal

    def __init__(self):
        super().__init__()
        loadUi("ui/network.ui", self)
        self.setWindowIcon(QIcon("ui/icon.ico"))
        # View native IP
        self.showIpButton.clicked.connect(lambda: QMessageBox.information(self, 'View native IP', socket.gethostbyname(
            socket.gethostname()), QMessageBox.Ok))
        self.startServerButton.clicked.connect(self.buildServerConnection)
        self.connectButton.clicked.connect(self.buildClientConnection)
        self.sendTreeButton.clicked.connect(self.sendTree)
        self.sendTextButton.clicked.connect(self.sendText)
        self.setEncodedTextSign.connect(self.setEncodedText)
        self.breakButton.clicked.connect(self.breakConnection)
        # Server side input restrictions
        self.lineEdit.setValidator(QRegExpValidator(QRegExp(
            r'((6553[0-5])|[655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])'), self))
        # Client port number input limit
        self.connectPortEditText.setValidator(QRegExpValidator(QRegExp(
            r'((6553[0-5])|[655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{3}|[1-9][0-9]{2}|[1-9][0-9]|[0-9])'), self))
        # Client ip address input restrictions
        self.connectIpEditText.setValidator(QRegExpValidator(QRegExp(
            r'((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])[\\.]){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])'), self))

    def buildServerConnection(self):
        # Server listening port
        try:
            # Get port number
            port = int(self.lineEdit.text())
        except ValueError:
            QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok)
            return
        # Create a socket
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            # Set the listening port and listen
            s.bind(("0.0.0.0", port))
            s.listen()
        except OSError:
            QMessageBox.critical(self, "error", "The port is already occupied", QMessageBox.Ok)
            return
        # Waiting for client connection
        self.stateLabel.setText("Waiting for connection")
        # Start a new thread to wait for the connection to prevent the program from blocking, and use the daemon flag to automatically end all threads with this flag when the main thread ends
        threading.Thread(target=self.handleClient,
                         args=[s], daemon=True).start()

    def handleClient(self, s: socket.socket):
        # Server side waiting for connection
        c = s.accept()[0]
        self.stateLabel.setText("Connected")
        self.s = c
        # Start the thread waiting to receive
        threading.Thread(target=self.waitRecv, args=[c], daemon=True).start()

    def buildClientConnection(self):
        # Client establish connection
        try:
            # Get IP address
            ip = self.connectIpEditText.text()
            if ip == None:
                QMessageBox.critical(
                    self, "error", "There are currently no entered IP address", QMessageBox.Ok)
                return
            port = int(self.connectPortEditText.text())
        except ValueError:
            QMessageBox.critical(self, "error", "There is currently no port number entered", QMessageBox.Ok)
            return
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            s.connect((ip, port))
        except ConnectionRefusedError:
            QMessageBox.critical(self, "error", "connection failed", QMessageBox.Ok)
            return
        except OSError:
            QMessageBox.critical(self, "error", "IP Or port error", QMessageBox.Ok)
            return
        self.stateLabel.setText("Connected")
        self.s = s
        # The connection is successful. Start the thread waiting to receive
        threading.Thread(target=self.waitRecv, args=[s], daemon=True).start()

    def sendTree(self):
        # Send tree
        if not self.s:
            QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok)
            return
        global CharacterSet
        if not CharacterSet or not HFTree:
            QMessageBox.critical(self, "error", "The current tree is empty", QMessageBox.Ok)
            return
        content = 't'  # Flag of sending tree
        for i, j in CharacterSet.items():
            content += i+"\t"+str(j)+'\n'
        # Convert it to Byte for sending
        self.s.sendall(content.encode())
        QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok)

    def sendText(self):
        # Send ciphertext
        if not self.s:
            QMessageBox.critical(self, "error", "Please establish a connection first", QMessageBox.Ok)
            return
        global encodedTextEdit
        content = encodedTextEdit.toPlainText()
        if not checkDecodedText(content):
            QMessageBox.critical(self, "error", "There are invalid characters", QMessageBox.Ok)
            return
        self.s.sendall(('c'+content).encode())
        QMessageBox.information(self, "Tips", "Sent successfully", QMessageBox.Ok)

    def setEncodedText(self, text):
        # Enter the received ciphertext into the text box
        global encodedTextEdit
        encodedTextEdit.setText(text)

    def waitRecv(self, s: socket.socket):
        # Waiting to accept thread
        try:
            while True:
                data = s.recv(10000000)
                # Convert content to str type
                data = data.decode()
                if data[0] == 't':
                    data = data[1:]
                    textlines = re.findall(r'([\s\S])\t(\S+)(\n|$)', data)
                    global CharacterSet
                    CharacterSet = {}
                    for i, j, _ in textlines:
                        try:
                            CharacterSet[i] = float(j)
                        except ValueError:
                            self.stateLabel.setText("Useless data received")
                            self.tableWidget.clearContents()
                            self.tableWidget.setRowCount(0)
                            CharacterSet = {}
                            return
                    global HFTree
                    if CharacterSet != {}:
                        HFTree = HuffmanTree(CharacterSet)
                        self.stateLabel.setText("Tree received")
                    else:
                        self.stateLabel.setText("Empty tree received")
                elif data[0] == 'c':
                    self.stateLabel.setText("Ciphertext received")
                    data = data[1:]
                    self.setEncodedTextSign.emit(data)
                else:
                    self.stateLabel.setText("Useless data received")
        except ConnectionResetError:  # The other party disconnected
            self.stateLabel.setText("Disconnected")
            self.s = None
        except ConnectionAbortedError:  # Disconnect yourself
            pass

    def breakConnection(self):
        # Disconnect button event
        try:
            self.s.close()
            self.s = None
            self.stateLabel.setText("Not connected")
        except:
            pass

class PaintTreeWindow(QWidget):
    # Form showing tree
    height: int = 0
    CharacterSet = {}
    paintingLayout: QVBoxLayout

    def __init__(self):
        super().__init__()
        loadUi("ui/tree.ui", self)
        self.setWindowIcon(QIcon("ui/icon.ico"))
        self.findCodeButton.clicked.connect(charsetWindow.show)
        self.saveButton.clicked.connect(self.savetree)
        self.loadButton.clicked.connect(self.importtree)
        global showSVGWidget
        showSVGWidget = ShowSVGWidget(self)
        self.paintingLayout.addWidget(showSVGWidget)

    def showEvent(self, e):
        # Refresh the picture of the tree on open
        if not HFTree:
            QMessageBox.critical(self, "error", "There is no built tree at present", QMessageBox.Ok)

    def TreeDepth(self, pRoot: HuffmanTree):
        # Calculate the depth of the tree
        def currentTreeDepth(pRoot: TreeNode):
            if pRoot is None:
                return 0
            if pRoot.lchild or pRoot.rchild:
                return max(currentTreeDepth(pRoot.lchild), currentTreeDepth(pRoot.rchild))+1
            else:
                return 1
        return currentTreeDepth(pRoot.rootnode)

    def printInform(self):
        # Update tree information
        self.treeHeightlabel.setText(str(self.TreeDepth(HFTree)))
        self.nodeCountlabel.setText(str(len(HFTree.characterset)*2-1))
        self.leafCountlabel.setText(str(len(HFTree.characterset)))

    def importtree(self):
        # Import the tree information into the picture
        filePath, ok = QFileDialog.getOpenFileName(self, 'Select file')
        if ok:
            with open(filePath, 'r', encoding='utf-8') as file:
                try:
                    text = file.read()
                except UnicodeDecodeError:
                    QMessageBox.critical(
                        self, "error", "Please make sure that the is open UTF-8 Encoded text file", QMessageBox.Ok)
                    return
            global CharacterSet
            CharacterSet = self.CharacterSet
            textlines = re.findall(r'([\s\S])\t(\S+)\t\S+(\n|$)', text)
            # Reset the character set information after import and update the tree in memory
            for i, j, _ in textlines:
                CharacterSet[i] = float(j)
            global HFTree
            if CharacterSet != {}:
                HFTree = HuffmanTree(CharacterSet)
                global showSVGWidget
                HFTree.printTree('tmp')
                showSVGWidget.update()
                self.printInform()  # Write the tree information on the panel

    def savetree(self):
        # Save tree information
        filePath, ok = QFileDialog.getSaveFileName(self, 'Select file')
        if ok:
            with open(filePath, 'w', encoding='utf-8') as file:
                for i, j in HFTree.characterset.items():
                    m = '\t'.join([i, str(j), HFTree.encode(i)])
                    file.write(m+'\n')

class ShowSVGWidget(QWidget):
    # Custom control to display svg pictures
    leftClick: bool
    svgrender: QSvgRenderer
    defaultSize: QSizeF
    point: QPoint
    scale = 1

    def __init__(self, parent=None):
        super().__init__(parent)
        self.parent = parent
        # Construct a blank svg image
        self.svgrender = QSvgRenderer(
            b'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 0 0"  width="512pt" height="512pt"></svg>')
        # Get picture default size
        self.defaultSize = QSizeF(self.svgrender.defaultSize())
        self.point = QPoint(0, 0)
        self.scale = 1

    def update(self):
        # Update picture
        self.svgrender = QSvgRenderer("tmp.svg")
        self.defaultSize = QSizeF(self.svgrender.defaultSize())
        self.point = QPoint(0, 0)
        self.scale = 1
        self.repaint()

    def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
        # Painting event (callback function)
        painter = QPainter()  # paint brush
        painter.begin(self)
        self.svgrender.render(painter, QRectF(
            self.point, self.defaultSize*self.scale))  # svg renderer to paint, (brush, qrectf (position, size)) (F means float)
        painter.end()

    def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
        # Mouse movement event (callback function)
        if self.leftClick:
            self.endPos = a0.pos()-self.startPos
            self.point += self.endPos
            self.startPos = a0.pos()
            self.repaint()

    def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None:
        # Mouse click event (callback function)
        if a0.button() == Qt.LeftButton:
            self.leftClick = True
            self.startPos = a0.pos()

    def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None:
        # Mouse release event (callback function)
    
    if a0.button() == Qt.LeftButton:
            self.leftClick = False

    def wheelEvent(self, a0: QtGui.QWheelEvent) -> None:
        # Zoom the image according to the position of the cursor
        oldScale = self.scale
        if a0.angleDelta().y() > 0:
            # enlarge
            if self.scale <= 5.0:
                self.scale *= 1.1
        elif a0.angleDelta().y() < 0:
            # narrow
            if self.scale >= 0.2:
                self.scale *= 0.9
        self.point = a0.pos()-(self.scale/oldScale*(a0.pos()-self.point))
        self.repaint()

# if __name__ == '__main__':
multiprocessing.freeze_support()
app = QApplication(sys.argv)
mainWindow = MainWindow()
charsetWindow = CharsetWindow()
nettansportWindow = NetTransportWindow()
paintTreeWindow = PaintTreeWindow()
mainWindow.show()
mainWindow.editFrequencyButton.clicked.connect(charsetWindow.show)
mainWindow.networkTransportButton.clicked.connect(nettansportWindow.show)
mainWindow.paintTreeButton.clicked.connect(paintTreeWindow.show)
sys.exit(app.exec_())

VII Debugging analysis

  1. Initialization
    There are two forms of initialization: one is generated directly from the original text, and the other is based on importing characters directly

  2. Encoding
  3. Decoding
  4. Print code file (print)
  5. Print Huffman Tree printing
  6. Network communication
Disadvantages:
  1. The input of ciphertext cannot be restricted (because textedit has extremely rich functions, such as pasting with format, it cannot restrict the format of ciphertext, and can only judge the content during transmission, reading and writing)
  2. After the server is disconnected from the client, the server cannot wait for the next reconnection
  3. The length of each reception shall not exceed 10000000
  4. Due to the precision of floating point numbers, 6.0 and 6 may not be the same
  5. The process of saving pictures is complicated
advantage:
  1. Regular expressions are used to restrict the input of characters, word frequency, port number and ip address
  2. Special judgment on various illegal operations
  3. Zoom the image according to the position of the cursor, and draw with a vector diagram without distortion when zooming in and out

8, Experimental experience (summary)

Through the study of this data structure course design, I have a deeper understanding of the algorithms in the data structure, especially the construction of Huffman tree and the coding and decoding of Huffman encoder. There are many bugs in writing code, but after continuous debugging, the previously incomprehensible code is more familiar. When there is a bug in your program, you should first check whether there are problems in some small details of your program, and find the error and improve it through the error reporting of the compiler. If you can't find the problem, you need to borrow online materials and help yourself complete the debugging of the program through their experience.
Through this course design, I have enhanced my ability to design programs independently and debug programs, which has benefited me a lot.

Keywords: Python regex PyQt5 Network Communications

Added by IRON FART on Sun, 23 Jan 2022 21:02:07 +0200