diff --git a/client/MainWindow.py b/client/MainWindow.py index ea64ca6..aa7c091 100644 --- a/client/MainWindow.py +++ b/client/MainWindow.py @@ -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...")) diff --git a/client/MainWindow.ui b/client/MainWindow.ui index dcd03b8..d836ca3 100644 --- a/client/MainWindow.ui +++ b/client/MainWindow.ui @@ -7,7 +7,7 @@ 0 0 800 - 609 + 651 @@ -21,48 +21,61 @@ - - - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - - - - - - QLayout::SetDefaultConstraint + + + + Connections - - - - - 0 - 0 - - - - - 16777215 - 70 - - - - - + + + + + + + 0 + 0 + + + + + 16777215 + 80 + + + + + + + + + 0 + 5 + + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + + + + + Chat History + + diff --git a/client/Nickname.ui b/client/Nickname.ui new file mode 100644 index 0000000..0122111 --- /dev/null +++ b/client/Nickname.ui @@ -0,0 +1,96 @@ + + + NicknameDialog + + + + 0 + 0 + 449 + 85 + + + + Dialog + + + + + + + + + Qt::Vertical + + + QSizePolicy::Maximum + + + + 20 + 10 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + buttonBox + accepted() + NicknameDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + NicknameDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/client/dialog.py b/client/dialog.py new file mode 100644 index 0000000..670f3d5 --- /dev/null +++ b/client/dialog.py @@ -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() diff --git a/client/gui.py b/client/gui.py index b3cf01f..e7097d7 100644 --- a/client/gui.py +++ b/client/gui.py @@ -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) diff --git a/client/gui.ui b/client/gui.ui deleted file mode 100644 index d836ca3..0000000 --- a/client/gui.ui +++ /dev/null @@ -1,120 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 800 - 651 - - - - MainWindow - - - - - 0 - 0 - - - - - - - Connections - - - - - - - - 0 - 0 - - - - - 16777215 - 80 - - - - - - - - - 0 - 5 - - - - - - - - - 0 - 0 - - - - - 200 - 16777215 - - - - - - - - Chat History - - - - - - - - - 0 - 0 - 800 - 21 - - - - - File - - - - - - - - - - - Connect to... - - - - - Export chat... - - - - - Quit - - - - - - diff --git a/client/nickname.py b/client/nickname.py new file mode 100644 index 0000000..ad0ebd1 --- /dev/null +++ b/client/nickname.py @@ -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"))