From 9f317f3b96c231ec6cdc7f6a0a63c6c55ad7ced0 Mon Sep 17 00:00:00 2001 From: Xevion Date: Mon, 25 Jan 2021 11:25:58 -0600 Subject: [PATCH] new ConnectionDialog for managing server settings, nickname and password profile with input validation, statusbar tips --- client/ConnectionDialog.py | 176 ++++++++++++++++++ client/dialog.py | 57 +++++- client/ui/ConnectionDialog.ui | 329 ++++++++++++++++++++++++++++++++++ client/{ => ui}/MainWindow.ui | 0 client/{ => ui}/Nickname.ui | 0 5 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 client/ConnectionDialog.py create mode 100644 client/ui/ConnectionDialog.ui rename client/{ => ui}/MainWindow.ui (100%) rename client/{ => ui}/Nickname.ui (100%) diff --git a/client/ConnectionDialog.py b/client/ConnectionDialog.py new file mode 100644 index 0000000..a521d15 --- /dev/null +++ b/client/ConnectionDialog.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\client\ui\ConnectionDialog.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_ConnectionDialog(object): + def setupUi(self, ConnectionDialog): + ConnectionDialog.setObjectName("ConnectionDialog") + ConnectionDialog.setWindowModality(QtCore.Qt.WindowModal) + ConnectionDialog.resize(665, 450) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(3) + sizePolicy.setHeightForWidth(ConnectionDialog.sizePolicy().hasHeightForWidth()) + ConnectionDialog.setSizePolicy(sizePolicy) + ConnectionDialog.setMinimumSize(QtCore.QSize(660, 450)) + self.gridLayout = QtWidgets.QGridLayout(ConnectionDialog) + self.gridLayout.setObjectName("gridLayout") + self.server_connections_tab = QtWidgets.QTabWidget(ConnectionDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(2) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.server_connections_tab.sizePolicy().hasHeightForWidth()) + self.server_connections_tab.setSizePolicy(sizePolicy) + self.server_connections_tab.setToolTip("") + self.server_connections_tab.setTabPosition(QtWidgets.QTabWidget.North) + self.server_connections_tab.setTabShape(QtWidgets.QTabWidget.Rounded) + self.server_connections_tab.setObjectName("server_connections_tab") + self.favorites_tab = QtWidgets.QWidget() + self.favorites_tab.setObjectName("favorites_tab") + self.gridLayout_3 = QtWidgets.QGridLayout(self.favorites_tab) + self.gridLayout_3.setContentsMargins(0, 0, 0, 0) + self.gridLayout_3.setObjectName("gridLayout_3") + self.favorite_connections_list = QtWidgets.QListWidget(self.favorites_tab) + self.favorite_connections_list.setToolTip("") + self.favorite_connections_list.setFrameShape(QtWidgets.QFrame.NoFrame) + self.favorite_connections_list.setObjectName("favorite_connections_list") + self.gridLayout_3.addWidget(self.favorite_connections_list, 0, 0, 1, 1) + self.server_connections_tab.addTab(self.favorites_tab, "") + self.recent_tab = QtWidgets.QWidget() + self.recent_tab.setAutoFillBackground(False) + self.recent_tab.setObjectName("recent_tab") + self.gridLayout_2 = QtWidgets.QGridLayout(self.recent_tab) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setHorizontalSpacing(7) + self.gridLayout_2.setObjectName("gridLayout_2") + self.recent_connections_list = QtWidgets.QListView(self.recent_tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.recent_connections_list.sizePolicy().hasHeightForWidth()) + self.recent_connections_list.setSizePolicy(sizePolicy) + self.recent_connections_list.setAutoFillBackground(False) + self.recent_connections_list.setFrameShape(QtWidgets.QFrame.NoFrame) + self.recent_connections_list.setFrameShadow(QtWidgets.QFrame.Plain) + self.recent_connections_list.setLineWidth(1) + self.recent_connections_list.setObjectName("recent_connections_list") + self.gridLayout_2.addWidget(self.recent_connections_list, 0, 0, 1, 1) + self.server_connections_tab.addTab(self.recent_tab, "") + self.gridLayout.addWidget(self.server_connections_tab, 0, 0, 1, 1) + self.connection_groupbox = QtWidgets.QGroupBox(ConnectionDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(1) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.connection_groupbox.sizePolicy().hasHeightForWidth()) + self.connection_groupbox.setSizePolicy(sizePolicy) + self.connection_groupbox.setMinimumSize(QtCore.QSize(0, 100)) + self.connection_groupbox.setObjectName("connection_groupbox") + self.gridLayout_4 = QtWidgets.QGridLayout(self.connection_groupbox) + self.gridLayout_4.setObjectName("gridLayout_4") + self.connect_layout = QtWidgets.QHBoxLayout() + self.connect_layout.setObjectName("connect_layout") + self.test_connection_button = QtWidgets.QPushButton(self.connection_groupbox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(35) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.test_connection_button.sizePolicy().hasHeightForWidth()) + self.test_connection_button.setSizePolicy(sizePolicy) + self.test_connection_button.setFocusPolicy(QtCore.Qt.NoFocus) + self.test_connection_button.setObjectName("test_connection_button") + self.connect_layout.addWidget(self.test_connection_button) + self.connect_button = QtWidgets.QPushButton(self.connection_groupbox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(100) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.connect_button.sizePolicy().hasHeightForWidth()) + self.connect_button.setSizePolicy(sizePolicy) + self.connect_button.setFocusPolicy(QtCore.Qt.NoFocus) + self.connect_button.setObjectName("connect_button") + self.connect_layout.addWidget(self.connect_button) + self.gridLayout_4.addLayout(self.connect_layout, 4, 0, 1, 1) + self.server_address_layout = QtWidgets.QHBoxLayout() + self.server_address_layout.setObjectName("server_address_layout") + self.server_address_label = QtWidgets.QLabel(self.connection_groupbox) + self.server_address_label.setObjectName("server_address_label") + self.server_address_layout.addWidget(self.server_address_label) + self.server_address_input = QtWidgets.QLineEdit(self.connection_groupbox) + self.server_address_input.setInputMethodHints(QtCore.Qt.ImhPreferNumbers) + self.server_address_input.setObjectName("server_address_input") + self.server_address_layout.addWidget(self.server_address_input) + self.port_label = QtWidgets.QLabel(self.connection_groupbox) + self.port_label.setObjectName("port_label") + self.server_address_layout.addWidget(self.port_label) + self.port_input = QtWidgets.QLineEdit(self.connection_groupbox) + self.port_input.setInputMethodHints(QtCore.Qt.ImhDigitsOnly) + self.port_input.setObjectName("port_input") + self.server_address_layout.addWidget(self.port_input) + self.gridLayout_4.addLayout(self.server_address_layout, 0, 0, 1, 1) + self.nickname_layout = QtWidgets.QHBoxLayout() + self.nickname_layout.setObjectName("nickname_layout") + self.nickname_label = QtWidgets.QLabel(self.connection_groupbox) + self.nickname_label.setObjectName("nickname_label") + self.nickname_layout.addWidget(self.nickname_label) + self.nickname_input = QtWidgets.QLineEdit(self.connection_groupbox) + self.nickname_input.setObjectName("nickname_input") + self.nickname_layout.addWidget(self.nickname_input) + self.password_label = QtWidgets.QLabel(self.connection_groupbox) + self.password_label.setObjectName("password_label") + self.nickname_layout.addWidget(self.password_label) + self.password_input = QtWidgets.QLineEdit(self.connection_groupbox) + self.password_input.setEchoMode(QtWidgets.QLineEdit.PasswordEchoOnEdit) + self.password_input.setObjectName("password_input") + self.nickname_layout.addWidget(self.password_input) + self.remember_checkbox = QtWidgets.QCheckBox(self.connection_groupbox) + self.remember_checkbox.setObjectName("remember_checkbox") + self.nickname_layout.addWidget(self.remember_checkbox) + self.gridLayout_4.addLayout(self.nickname_layout, 1, 0, 1, 1) + self.gridLayout.addWidget(self.connection_groupbox, 2, 0, 3, 1) + spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + self.gridLayout.addItem(spacerItem, 1, 0, 1, 1) + self.status_layout = QtWidgets.QHBoxLayout() + self.status_layout.setObjectName("status_layout") + self.gridLayout.addLayout(self.status_layout, 5, 0, 1, 1) + + self.retranslateUi(ConnectionDialog) + self.server_connections_tab.setCurrentIndex(1) + QtCore.QMetaObject.connectSlotsByName(ConnectionDialog) + + def retranslateUi(self, ConnectionDialog): + _translate = QtCore.QCoreApplication.translate + ConnectionDialog.setWindowTitle(_translate("ConnectionDialog", "Connect to Server")) + self.server_connections_tab.setStatusTip(_translate("ConnectionDialog", "Connect to your favorite or most recent servers")) + self.favorites_tab.setStatusTip(_translate("ConnectionDialog", "Servers that you have set as favorites in the Recent tab.")) + self.favorite_connections_list.setStatusTip(_translate("ConnectionDialog", "Servers that you have set as favorites in the Recent tab.")) + self.server_connections_tab.setTabText(self.server_connections_tab.indexOf(self.favorites_tab), _translate("ConnectionDialog", "Favorites")) + self.recent_tab.setStatusTip(_translate("ConnectionDialog", "Servers that you have connected to most recently.")) + self.recent_connections_list.setStatusTip(_translate("ConnectionDialog", "Servers that you have connected to most recently.")) + self.server_connections_tab.setTabText(self.server_connections_tab.indexOf(self.recent_tab), _translate("ConnectionDialog", "Recent")) + self.connection_groupbox.setStatusTip(_translate("ConnectionDialog", "Edit your connection settings.")) + self.connection_groupbox.setTitle(_translate("ConnectionDialog", "Connection Settings")) + self.test_connection_button.setStatusTip(_translate("ConnectionDialog", "Test your ability to connect to the server")) + self.test_connection_button.setText(_translate("ConnectionDialog", "Test Connection")) + self.connect_button.setStatusTip(_translate("ConnectionDialog", "Connect to the TCPChat server immediately")) + self.connect_button.setText(_translate("ConnectionDialog", "Connect")) + self.server_address_label.setText(_translate("ConnectionDialog", "Server Address")) + self.server_address_input.setStatusTip(_translate("ConnectionDialog", "A IPv4 address to the server host, local or not.")) + self.server_address_input.setPlaceholderText(_translate("ConnectionDialog", "127.0.0.1")) + self.port_label.setText(_translate("ConnectionDialog", "Port")) + self.port_input.setStatusTip(_translate("ConnectionDialog", "The port number the server is running on.")) + self.port_input.setPlaceholderText(_translate("ConnectionDialog", "5555")) + self.nickname_label.setText(_translate("ConnectionDialog", "Nickname")) + self.nickname_input.setStatusTip(_translate("ConnectionDialog", "Your human identifier. Without a password, uniqueness is not ensured.")) + self.nickname_input.setPlaceholderText(_translate("ConnectionDialog", "Type your nickname here...")) + self.password_label.setText(_translate("ConnectionDialog", "Password")) + self.password_input.setStatusTip(_translate("ConnectionDialog", "A optional password for preserving your nickname with.")) + self.password_input.setPlaceholderText(_translate("ConnectionDialog", "Optional")) + self.remember_checkbox.setStatusTip(_translate("ConnectionDialog", "Remember this password in plaintext. Kept in Recent and/or Favorites.")) + self.remember_checkbox.setText(_translate("ConnectionDialog", "Remember")) diff --git a/client/dialog.py b/client/dialog.py index 670f3d5..024b8cc 100644 --- a/client/dialog.py +++ b/client/dialog.py @@ -1,6 +1,14 @@ -from PyQt5.QtWidgets import QDialog +import re +from typing import Tuple +from PyQt5 import QtCore, QtGui +from PyQt5.QtCore import QEvent +from PyQt5.QtWidgets import QDialog, QStatusBar, QWidget + +import constants +from client.ConnectionDialog import Ui_ConnectionDialog from client.nickname import Ui_NicknameDialog +from constants import ConnectionOptions class NicknameDialog(QDialog, Ui_NicknameDialog): @@ -33,3 +41,50 @@ class NicknameDialog(QDialog, Ui_NicknameDialog): """Tries to submit the Dialog through the QLineEdit via Enter key""" if not self.disabled: self.accept() + + +class ConnectionDialog(QDialog, Ui_ConnectionDialog): + def __init__(self, *args, **kwargs): + super(ConnectionDialog, self).__init__(*args, **kwargs) + self.setupUi(self) + + self.connect_button.setDisabled(True) + self.server_address_input.textEdited.connect(self.validation) + self.port_input.textEdited.connect(self.validation) + self.nickname_input.textEdited.connect(self.validation) + self.connect_button.pressed.connect(self.connect) + + self.status_bar = QStatusBar(self) + self.status_layout.addWidget(self.status_bar) + + self.connect_pressed = False + + self.show() + + def validation(self, full: bool = True) -> None: + address, port = self.validate_address() + + if not address and not port: + self.status_bar.showMessage('Please fill in a valid server address and port.', 3000) + elif not address: + self.status_bar.showMessage('Please fill in a valid server address.', 3000) + elif not port: + self.status_bar.showMessage('Please fill in a valid port number.', 3000) + elif full and not self.validate_nickname(): + self.status_bar.showMessage('Please use a valid nickname. Letters and digits, 3-15 characters long.', 3000) + + self.connect_button.setDisabled(not (self.validate_address() and self.validate_nickname())) + + def validate_nickname(self) -> bool: + """Returns True if the nickname follows the nickname guidelines requested.""" + return re.match(r'^[A-z0-9]{3,15}$', self.nickname_input.text()) is not None + + def validate_address(self) -> Tuple[bool, bool]: + """Returns True if the server address and port combination is valid""" + address = self.server_address_input.text() or constants.DEFAULT_IP + port = self.port_input.text() or constants.DEFAULT_PORT + + valid_address = len(address) > 0 and re.match(r'^\d{1,4}\.\d{1,4}\.\d{1,4}\.\d{1,4}|localhost$', address) is not None + valid_port = len(port) > 0 and re.match(r'^\d{4,5}$', port) is not None and 1024 <= int(port) <= 65536 + + return valid_address, valid_port diff --git a/client/ui/ConnectionDialog.ui b/client/ui/ConnectionDialog.ui new file mode 100644 index 0000000..6f558f9 --- /dev/null +++ b/client/ui/ConnectionDialog.ui @@ -0,0 +1,329 @@ + + + ConnectionDialog + + + Qt::WindowModal + + + + 0 + 0 + 665 + 450 + + + + + 0 + 3 + + + + + 660 + 450 + + + + Connect to Server + + + + + + + 2 + 0 + + + + + + + Connect to your favorite or most recent servers + + + QTabWidget::North + + + QTabWidget::Rounded + + + 1 + + + + Servers that you have set as favorites in the Recent tab. + + + Favorites + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Servers that you have set as favorites in the Recent tab. + + + QFrame::NoFrame + + + + + + + + Servers that you have connected to most recently. + + + false + + + Recent + + + + 0 + + + 0 + + + 0 + + + 0 + + + 7 + + + + + + 0 + 0 + + + + Servers that you have connected to most recently. + + + false + + + QFrame::NoFrame + + + QFrame::Plain + + + 1 + + + + + + + + + + + + 1 + 1 + + + + + 0 + 100 + + + + Edit your connection settings. + + + Connection Settings + + + + + + + + + 35 + 0 + + + + Qt::NoFocus + + + Test your ability to connect to the server + + + Test Connection + + + + + + + + 100 + 0 + + + + Qt::NoFocus + + + Connect to the TCPChat server immediately + + + Connect + + + + + + + + + + + Server Address + + + + + + + A IPv4 address to the server host, local or not. + + + Qt::ImhPreferNumbers + + + 127.0.0.1 + + + + + + + Port + + + + + + + The port number the server is running on. + + + Qt::ImhDigitsOnly + + + 5555 + + + + + + + + + + + Nickname + + + + + + + Your human identifier. Without a password, uniqueness is not ensured. + + + Type your nickname here... + + + + + + + Password + + + + + + + A optional password for preserving your nickname with. + + + QLineEdit::PasswordEchoOnEdit + + + Optional + + + + + + + Remember this password in plaintext. Kept in Recent and/or Favorites. + + + Remember + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 5 + + + + + + + + + + + + diff --git a/client/MainWindow.ui b/client/ui/MainWindow.ui similarity index 100% rename from client/MainWindow.ui rename to client/ui/MainWindow.ui diff --git a/client/Nickname.ui b/client/ui/Nickname.ui similarity index 100% rename from client/Nickname.ui rename to client/ui/Nickname.ui