switch to type/request based JSON messages, improve GUI, add proper nickname dialog

This commit is contained in:
Xevion
2021-01-08 18:42:03 -06:00
parent 390595c0e2
commit b792d9cb9c
7 changed files with 326 additions and 233 deletions

View File

@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 609)
MainWindow.resize(800, 651)
self.centralwidget = QtWidgets.QWidget(MainWindow)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
@@ -24,31 +24,38 @@ class Ui_MainWindow(object):
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 2, 1, 1)
self.messageBox = QtWidgets.QTextEdit(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Maximum)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.messageBox.sizePolicy().hasHeightForWidth())
self.messageBox.setSizePolicy(sizePolicy)
self.messageBox.setMaximumSize(QtCore.QSize(16777215, 80))
self.messageBox.setObjectName("messageBox")
self.gridLayout.addWidget(self.messageBox, 2, 1, 1, 2)
self.messageHistory = QtWidgets.QTextBrowser(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.textBrowser.sizePolicy().hasHeightForWidth())
self.textBrowser.setSizePolicy(sizePolicy)
self.textBrowser.setMaximumSize(QtCore.QSize(16777215, 16777215))
self.textBrowser.setObjectName("textBrowser")
self.horizontalLayout_2.addWidget(self.textBrowser)
self.gridLayout.addLayout(self.horizontalLayout_2, 0, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
self.horizontalLayout.setObjectName("horizontalLayout")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setVerticalStretch(5)
sizePolicy.setHeightForWidth(self.messageHistory.sizePolicy().hasHeightForWidth())
self.messageHistory.setSizePolicy(sizePolicy)
self.messageHistory.setObjectName("messageHistory")
self.gridLayout.addWidget(self.messageHistory, 1, 1, 1, 1)
self.connectionsList = QtWidgets.QListWidget(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.textEdit.sizePolicy().hasHeightForWidth())
self.textEdit.setSizePolicy(sizePolicy)
self.textEdit.setMaximumSize(QtCore.QSize(16777215, 70))
self.textEdit.setObjectName("textEdit")
self.horizontalLayout.addWidget(self.textEdit)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)
sizePolicy.setHeightForWidth(self.connectionsList.sizePolicy().hasHeightForWidth())
self.connectionsList.setSizePolicy(sizePolicy)
self.connectionsList.setMaximumSize(QtCore.QSize(200, 16777215))
self.connectionsList.setObjectName("connectionsList")
self.gridLayout.addWidget(self.connectionsList, 1, 2, 1, 1)
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 0, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
@@ -76,6 +83,8 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "Connections"))
self.label_2.setText(_translate("MainWindow", "Chat History"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.actionConnect_to.setText(_translate("MainWindow", "Connect to..."))
self.actionSave_chat_to.setText(_translate("MainWindow", "Export chat..."))

View File

@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>800</width>
<height>609</height>
<height>651</height>
</rect>
</property>
<property name="windowTitle">
@@ -21,48 +21,61 @@
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0">
<item>
<widget class="QTextBrowser" name="textBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
<item row="0" column="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Connections</string>
</property>
<item>
<widget class="QTextEdit" name="textEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>70</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QTextEdit" name="messageBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>80</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QTextBrowser" name="messageHistory">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>5</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QListWidget" name="connectionsList">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Chat History</string>
</property>
</widget>
</item>
</layout>
</widget>

96
client/Nickname.ui Normal file
View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NicknameDialog</class>
<widget class="QDialog" name="NicknameDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>449</width>
<height>85</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLineEdit" name="lineEdit"/>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NicknameDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NicknameDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

35
client/dialog.py Normal file
View File

@@ -0,0 +1,35 @@
from PyQt5.QtWidgets import QDialog
from client.nickname import Ui_NicknameDialog
class NicknameDialog(QDialog, Ui_NicknameDialog):
def __init__(self, *args, **kwargs):
super(NicknameDialog, self).__init__(*args, **kwargs)
self.setupUi(self)
self.disabled = True
self.buttonBox.setDisabled(True)
self.setWindowTitle("Set Nickname")
self.lineEdit.returnPressed.connect(self.trySubmit)
self.lineEdit.textEdited.connect(self.controlSubmit)
self.show()
def controlSubmit(self):
"""Updates whether or not the dialog box is allowed to proceed"""
if len(self.lineEdit.text()) > 2:
if self.disabled:
self.disabled = False
self.buttonBox.setDisabled(False)
else:
if not self.disabled:
self.disabled = True
self.buttonBox.setDisabled(True)
def trySubmit(self):
"""Tries to submit the Dialog through the QLineEdit via Enter key"""
if not self.disabled:
self.accept()

View File

@@ -1,11 +1,14 @@
import json
import socket
from pprint import pprint
import traceback
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QEvent
from PyQt5.QtWidgets import QMainWindow, QDialog, QDialogButtonBox, QVBoxLayout
from PyQt5.QtCore import QThread, pyqtSignal, Qt, QEvent, QTimer
from PyQt5.QtWidgets import QMainWindow
import constants
import helpers
from client.MainWindow import Ui_MainWindow
from config import config
from client.dialog import NicknameDialog
IP = '127.0.0.1'
PORT = 55555
@@ -15,6 +18,7 @@ HEADER_LENGTH = 10
class ReceiveWorker(QThread):
messages = pyqtSignal(str)
client_list = pyqtSignal(list)
error = pyqtSignal()
def __init__(self, client: socket.socket, nickname: str, parent=None):
@@ -25,73 +29,89 @@ class ReceiveWorker(QThread):
def run(self):
while True:
try:
length = int(self.client.recv(HEADER_LENGTH).decode('ascii'))
message = self.client.recv(length).decode('ascii')
if message == 'NICK':
header = f'{len(self.nickname):<{HEADER_LENGTH}}'
final = header + self.nickname
pprint(final)
self.client.send(final.encode('ascii'))
else:
self.messages.emit(message)
length = int(self.client.recv(HEADER_LENGTH).decode('utf-8'))
raw = self.client.recv(length).decode('utf-8')
if len(raw) == 0:
continue
message = json.loads(raw)
if message['type'] == constants.Types.REQUEST:
if message['request'] == constants.Requests.REQUEST_NICK:
self.client.send(helpers.prepare_json(
{
'type': constants.Types.NICKNAME,
'nickname': self.nickname
}
))
elif message['type'] == constants.Types.MESSAGE:
self.messages.emit(message['content'])
elif message['type'] == constants.Types.USER_LIST:
self.client_list.emit(message['users'])
except:
traceback.print_exc()
self.error.emit()
self.client.close()
break
class CustomDialog(QDialog):
def __init__(self, *args, **kwargs):
super(CustomDialog, self).__init__(*args, **kwargs)
self.setWindowTitle("HELLO!")
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox = QDialogButtonBox(buttons)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.layout = QVBoxLayout()
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
self.show()
# Get Nickname
nicknameDialog = NicknameDialog(self)
nicknameDialog.exec_()
self.nickname = nicknameDialog.lineEdit.text()
# Connect to server
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.connect((IP, PORT))
# nicknameDialog = CustomDialog(self)
# if nicknameDialog.exec_():
# print('s')
self.nickname = 'Default'
self.textEdit.installEventFilter(self)
# Setup message receiving thread worker
self.receiveThread = ReceiveWorker(self.client, self.nickname)
self.receiveThread.messages.connect(self.addMessage)
self.receiveThread.client_list.connect(self.updateConnectionsList)
self.receiveThread.start()
self.show()
self.connectionsListTimer = QTimer()
self.connectionsListTimer.timeout.connect(self.refreshConnectionsList)
self.connectionsListTimer.start(1000 * 30)
self.refreshConnectionsList()
self.messageBox.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() == QEvent.KeyPress and obj is self.textEdit:
if event.key() == Qt.Key_Return and self.textEdit.hasFocus():
self.sendMessage(self.textEdit.toPlainText())
self.textEdit.clear()
if event.type() == QEvent.KeyPress and obj is self.messageBox:
if event.key() == Qt.Key_Return and self.messageBox.hasFocus():
self.sendMessage(self.messageBox.toPlainText())
self.messageBox.clear()
self.messageBox.setText('')
cursor = self.messageBox.textCursor()
cursor.setPosition(0)
self.messageBox.setTextCursor(cursor)
return super().eventFilter(obj, event)
def addMessage(self, message: str) -> None:
self.textBrowser.append(message)
self.messageHistory.append(message)
def sendMessage(self, message: str) -> None:
header = f'{len(message):<{HEADER_LENGTH}}'
final = header + message
pprint(final)
self.client.send(final.encode('ascii'))
self.client.send(helpers.prepare_json(
{
'type': constants.Types.MESSAGE,
'content': message
}
))
def refreshConnectionsList(self):
self.client.send(helpers.prepare_json(
{
'type': constants.Types.REQUEST,
'request': constants.Requests.REFRESH_CONNECTIONS_LIST
}
))
def updateConnectionsList(self, users):
self.connectionsList.clear()
self.connectionsList.addItems(users)

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>651</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Connections</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QTextEdit" name="messageBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>80</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QTextBrowser" name="messageHistory">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>5</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QListWidget" name="connectionsList">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Chat History</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionConnect_to"/>
<addaction name="actionSave_chat_to"/>
<addaction name="actionQuit"/>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionConnect_to">
<property name="text">
<string>Connect to...</string>
</property>
</action>
<action name="actionSave_chat_to">
<property name="text">
<string>Export chat...</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

40
client/nickname.py Normal file
View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '.\Nickname.ui'
#
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_NicknameDialog(object):
def setupUi(self, NicknameDialog):
NicknameDialog.setObjectName("NicknameDialog")
NicknameDialog.resize(449, 85)
self.gridLayout = QtWidgets.QGridLayout(NicknameDialog)
self.gridLayout.setObjectName("gridLayout")
self.lineEdit = QtWidgets.QLineEdit(NicknameDialog)
self.lineEdit.setObjectName("lineEdit")
self.gridLayout.addWidget(self.lineEdit, 1, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 10, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum)
self.gridLayout.addItem(spacerItem, 2, 0, 1, 1)
self.buttonBox = QtWidgets.QDialogButtonBox(NicknameDialog)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.gridLayout.addWidget(self.buttonBox, 4, 0, 1, 1)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem1, 0, 0, 1, 1)
self.retranslateUi(NicknameDialog)
self.buttonBox.accepted.connect(NicknameDialog.accept)
self.buttonBox.rejected.connect(NicknameDialog.reject)
QtCore.QMetaObject.connectSlotsByName(NicknameDialog)
def retranslateUi(self, NicknameDialog):
_translate = QtCore.QCoreApplication.translate
NicknameDialog.setWindowTitle(_translate("NicknameDialog", "Dialog"))