├── icons ├── 1.png ├── send.png ├── user.png ├── smile1.png ├── smile10.png ├── smile11.png ├── smile12.png ├── smile13.png ├── smile14.png ├── smile15.png ├── smile2.png ├── smile3.png ├── smile4.png ├── smile5.png ├── smile6.png ├── smile7.png ├── smile8.png └── smile9.png ├── data ├── config.json └── servers.json ├── requirements.txt ├── README.md ├── methods ├── ConnectThreadMonitor.py ├── SettingsPanel.py └── windows │ ├── settings.ui │ └── settings.py ├── server └── server.py ├── main.py ├── des.py └── untitled.ui /icons/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/1.png -------------------------------------------------------------------------------- /icons/send.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/send.png -------------------------------------------------------------------------------- /icons/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/user.png -------------------------------------------------------------------------------- /icons/smile1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile1.png -------------------------------------------------------------------------------- /icons/smile10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile10.png -------------------------------------------------------------------------------- /icons/smile11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile11.png -------------------------------------------------------------------------------- /icons/smile12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile12.png -------------------------------------------------------------------------------- /icons/smile13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile13.png -------------------------------------------------------------------------------- /icons/smile14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile14.png -------------------------------------------------------------------------------- /icons/smile15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile15.png -------------------------------------------------------------------------------- /icons/smile2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile2.png -------------------------------------------------------------------------------- /icons/smile3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile3.png -------------------------------------------------------------------------------- /icons/smile4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile4.png -------------------------------------------------------------------------------- /icons/smile5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile5.png -------------------------------------------------------------------------------- /icons/smile6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile6.png -------------------------------------------------------------------------------- /icons/smile7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile7.png -------------------------------------------------------------------------------- /icons/smile8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile8.png -------------------------------------------------------------------------------- /icons/smile9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zproger/Messenger/HEAD/icons/smile9.png -------------------------------------------------------------------------------- /data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "nick": "admin", 3 | "server_ip": "127.0.0.1", 4 | "server_port": "5555" 5 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cffi==1.14.5 2 | colorama==0.4.4 3 | cryptography==3.4.7 4 | loguru==0.5.3 5 | pycparser==2.20 6 | PyQt5==5.15.4 7 | PyQt5-Qt5==5.15.2 8 | PyQt5-sip==12.9.0 9 | win32-setctime==1.0.3 10 | -------------------------------------------------------------------------------- /data/servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "server1": { 3 | "ip": "127.0.0.1", 4 | "port": "5555" 5 | }, 6 | "server2": { 7 | "ip": "127.0.0.1", 8 | "port": "4444" 9 | }, 10 | "server3": { 11 | "ip": "127.0.0.1", 12 | "port": "6666" 13 | } 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Messenger 2 | Мессенджер, который гарантирует приватность вашей переписки. 3 | Вы сами контролируете весь процесс. 4 | 5 | ### Мессенджер находится на этапе тестирования, поэтому возможны ошибки в работе ПО! 6 | 7 | ### Возможности 8 | - Все данные о личных сообщениях хранятся только в памяти и будут уничтожены после завершения сеанса 9 | - Возможность самому контролировать сервер и клиентов, которые подключаются 10 | - Возможность добавлять огромное количество серверов и переключаться между ними 11 | - Доступ к чату разрешен только людям, которые знают IP:PORT вашего сервера и имеют доступ к клиенту 12 | - Ключ шифрования генерируется каждый раз при попытке соединиться с сервером 13 | - Кроссплатформленность, клиент и сервер работают на любых системах с поддержкой Qt, Python, Socket 14 | - Возможность отслеживать входящие соединения 15 | 16 | ### Установка и использование 17 | 1. Устанавливаем Python 3.8 18 | 2. Устанавливаем зависимости и настраиваем проект 19 | 20 | ```sh 21 | git clone https://github.com/Zproger/Messenger.git 22 | cd Messenger 23 | pip3 install -r requirements.txt (на клиенте и сервере) 24 | ``` 25 | 26 | 3. Покупаем выделенный Ubuntu сервер с белым IP и отправляем на него файл server.py 27 | 4. Устанавливаем Tmux 28 | 29 | ```sh 30 | sudo apt update 31 | sudo apt install tmux (Для систем на Ubuntu) 32 | sudo pacman -S tmux (Для систем на Arch) 33 | ``` 34 | 35 | 5. Открываем сервер и редактируем IP:PORT на значения 0.0.0.0:4444 36 | 37 | ```python3 38 | if __name__ == "__main__": 39 | myserver = Server('0.0.0.0', 4444) 40 | ``` 41 | 42 | 6. Создаем сессию Tmux и запускаем сервер 43 | 44 | ```sh 45 | tmux new-session -t server 46 | python3 server.py 47 | Ctrl+B, D 48 | ``` 49 | 50 | 7. Запускаем main.py клиент, добавляем белый IP сервера и указываем порт 4444 51 | 8. Производим коннект и пользуемся 52 | 53 | ### Возможные ошибки 54 | 1. Перестал работать сервер 55 | - Проверьте работает ли сессия Tmux и ознакомьтесь с след.видео: 56 | https://www.youtube.com/watch?v=8pfMmx0LF8k 57 | 2. Любые ошибки клиента и сервера сохраняются в папке logs/ 58 | - Данная информация не отправляется на наши сервера, а хранится только у вас. 59 | Вы можете передать ошибку нам для дальнейшего исправления. 60 | 3. Клиент не подключается к серверу 61 | - Проверьте правильно ли указан белый IP сервера и порт 62 | - Проверьте установлены ли зависимости на сервере и клиенте 63 | - Смотрите 1-й пункт 64 | 65 | ### Демонстрация ПО 66 | ![software_main_window](icons/1.png) 67 | 68 | -------------------------------------------------------------------------------- /methods/ConnectThreadMonitor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import pickle 5 | from PyQt5 import QtCore 6 | from loguru import logger 7 | from cryptography.fernet import Fernet 8 | from methods.windows.settings import * 9 | 10 | 11 | # Мониторинг входящих сообщений 12 | class message_monitor(QtCore.QThread): 13 | mysignal = QtCore.pyqtSignal(list) 14 | server_socket = None 15 | symmetric_key = None 16 | 17 | def __init__(self, parent=None): 18 | QtCore.QThread.__init__(self, parent) 19 | 20 | def run(self): 21 | logger.info("Запущен потоковый обработчик") 22 | while True: 23 | if self.server_socket != None: 24 | try: 25 | message = self.server_socket.recv(7168) 26 | except OSError: 27 | return # Возникает при отключении от сервера, так как поток ожидаем данные по закрытому каналу связи 28 | 29 | pickle_dec = pickle.loads(message) 30 | 31 | # Если это запрос на заполнение ключей 32 | # ["SERVER_OK", "MESSAGE", "KEY"] 33 | if pickle_dec[0] == "SERVER_OK": 34 | logger.info(f"SERVER_OK: {pickle_dec}") 35 | self.symmetric_key = pickle_dec[-1] 36 | self.cipher = Fernet(self.symmetric_key) # Объект шифровальщика 37 | self.mysignal.emit(pickle_dec) 38 | 39 | # Если поступает пользовательское уведомление 40 | # pickle_dec[1] содержит сообщение 41 | elif pickle_dec[0] == "USERS_NOTIFY": 42 | logger.info(f"USERS_NOTIFY: {pickle_dec}") 43 | decrypted_payload = ["USERS_NOTIFY", pickle_dec[1]] 44 | self.mysignal.emit(decrypted_payload) 45 | 46 | # Обработка сообщений от других пользователей 47 | # ['ENCRYPT_MESSAGE', self.nick, smile_num, message_text.encode()] 48 | elif pickle_dec[0] == "ENCRYPT_MESSAGE": 49 | logger.info(f"ENCRYPT_MESSAGE: {pickle_dec}") 50 | decrypted_text = self.cipher.decrypt(pickle_dec[-1]).decode() 51 | decrypted_payload = ["ENCRYPT_MESSAGE", pickle_dec[1], pickle_dec[2], decrypted_text] 52 | self.mysignal.emit(decrypted_payload) 53 | time.sleep(0.5) 54 | 55 | # Отправить зашифрованное сообщение на сервер 56 | def send_encrypt(self, data_list): 57 | # ['ENCRYPT_MESSAGE', self.nick, smile_num, message_text.encode()] 58 | if data_list[0] == "ENCRYPT_MESSAGE": 59 | encrypted_message = self.cipher.encrypt(data_list[-1]) 60 | payload = pickle.dumps(['ENCRYPT_MESSAGE', data_list[1], data_list[2], encrypted_message]) 61 | 62 | try: 63 | self.server_socket.send(payload) 64 | except Exception as err: 65 | logger.exception("ConnectionError:") 66 | self.mysignal.emit(["CONNECTION_ERROR"]) 67 | return 68 | 69 | # Если нужно уведомить всех пользователей 70 | # ["USERS_NOTIFY", "message"] 71 | elif data_list[0] == "USERS_NOTIFY": 72 | payload = pickle.dumps(data_list) 73 | self.server_socket.send(payload) 74 | 75 | # Если клиент разорвал соединение 76 | # ['EXIT', f'{self.nick}', 'вышел из чата!'] 77 | elif data_list[0] == "EXIT": 78 | try: 79 | payload = pickle.dumps(data_list) 80 | self.server_socket.send(payload) 81 | # Если сервер отключен и мы пытаемся в этот 82 | # момент из него выйти 83 | except: 84 | pass -------------------------------------------------------------------------------- /server/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import pickle 5 | import socket 6 | import threading 7 | from loguru import logger 8 | from cryptography.fernet import Fernet 9 | 10 | 11 | # Настраиваем глобальный логгер для вывода в stdout 12 | # ============================================================================================================ 13 | logger.remove() 14 | logger.add( 15 | sink=sys.stdout, 16 | colorize=True, 17 | format="{time:HH:mm:ss} - {name}:{function}:{line} - {message}", 18 | level="DEBUG" 19 | ) 20 | 21 | # Конфигурация логгера, который работает с журналом 22 | logger.add( 23 | sink=os.path.join("logs", "server_log.txt"), 24 | format="{time:HH:mm:ss} - {name}:{function}:{line} - {message}", 25 | level="INFO", 26 | rotation="100 MB", 27 | compression="zip", 28 | enqueue=True # Делаем логгер потокобезопасным 29 | ) 30 | # ============================================================================================================ 31 | 32 | 33 | class Server: 34 | def __init__(self, ip, port): 35 | self.ip = ip 36 | self.port = port 37 | self.all_client = [] 38 | self.symmetric_key = None 39 | 40 | # Запускаем прослушивание соединений 41 | self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 42 | self.server.bind((self.ip, self.port)) 43 | self.server.listen(0) 44 | threading.Thread(target=self.connect_handler).start() 45 | logger.debug(f"Сервер запущен ({self.ip}:{self.port})") 46 | 47 | 48 | # Обрабатываем входящие соединения 49 | def connect_handler(self): 50 | logger.debug("Запущен обработчик входящих соединений") 51 | while True: 52 | client, address = self.server.accept() 53 | if client not in self.all_client: 54 | self.all_client.append(client) 55 | 56 | # Отправляем запрос на успешное подключение + раздаем симметричные ключи 57 | self.get_key() 58 | payload = ['SERVER_OK', "Успешное подключение к чату!", self.symmetric_key] 59 | client.send(pickle.dumps(payload)) 60 | logger.info(f'{address} - Успешное подключение к чату!') 61 | threading.Thread(target=self.message_handler, args=(client,)).start() 62 | time.sleep(2) 63 | 64 | 65 | # Отправляем сообщение всем клиентам кроме текущего 66 | # Принимает client_socket и сообщение для рассылки 67 | def sendall(self, current_socket, message): 68 | for client in self.all_client: 69 | if client != current_socket: 70 | client.send(message) 71 | 72 | 73 | # Обрабатываем отправленный текст 74 | def message_handler(self, client_socket): 75 | while True: 76 | try: 77 | message = client_socket.recv(7168) 78 | pickle_dec = pickle.loads(message) 79 | logger.debug(pickle_dec) 80 | except Exception as err: 81 | logger.exception(f"message_handler error:") 82 | self.all_client.remove(client_socket) 83 | logger.error(f"Неизвестный клиент удален с сервера из-за ошибки") 84 | break 85 | 86 | # Отправляем зашифрованное сообщение всем клиентам 87 | if pickle_dec[0] == "ENCRYPT_MESSAGE": 88 | self.sendall(client_socket, message) 89 | 90 | # Уведомление всем пользователям 91 | elif pickle_dec[0] == "USERS_NOTIFY": 92 | client_msg = pickle.dumps(["USERS_NOTIFY", pickle_dec[1]]) 93 | self.sendall(client_socket, client_msg) 94 | 95 | # Если клиент отправил запрос на дисконнект 96 | elif pickle_dec[0] == "EXIT": 97 | logger.debug(f'{pickle_dec[1]} - разорвал соединение!') 98 | logger.debug("=" * 50) 99 | 100 | # Отправляем уведомление всех клиентам 101 | client_msg = pickle.dumps(["USERS_NOTIFY", f"{pickle_dec[1]} - Покинул чат"]) 102 | self.sendall(client_socket, client_msg) 103 | self.all_client.remove(client_socket) 104 | break 105 | time.sleep(1) 106 | 107 | 108 | # Генератор сессионного симметричного ключа 109 | def get_key(self) -> None: 110 | if self.symmetric_key is None: 111 | logger.debug("Сгенерирован симметричный ключ шифрования") 112 | self.symmetric_key = Fernet.generate_key() 113 | 114 | 115 | if __name__ == "__main__": 116 | myserver = Server('127.0.0.1', 5555) 117 | -------------------------------------------------------------------------------- /methods/SettingsPanel.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import json 4 | import shutil 5 | from PyQt5 import QtCore, QtGui, QtWidgets 6 | from methods.windows.settings import * 7 | 8 | 9 | # Окно с настройками клиента 10 | class SettingPanel(QtWidgets.QWidget): 11 | def __init__(self, parent=None, signal=None): 12 | super().__init__(parent, QtCore.Qt.Window) 13 | self.setting = Ui_Form() 14 | self.setting.setupUi(self) 15 | self.setWindowModality(2) 16 | 17 | # Сигнал для возврата в интерфейс 18 | self.signal = signal 19 | 20 | # Отключаем стандартные границы окна программы 21 | self.setWindowFlag(QtCore.Qt.FramelessWindowHint) 22 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground) 23 | self.center() 24 | 25 | # Растягиваем таблицу на всю ширину окна и скрываем нумерацию 26 | header = self.setting.tableWidget.horizontalHeader() 27 | for position in range(3): 28 | header.setSectionResizeMode(position, QtWidgets.QHeaderView.Stretch) 29 | self.setting.tableWidget.verticalHeader().setVisible(False) 30 | 31 | # Инициализируем все параметры окна 32 | self.init_data() 33 | 34 | # Обработчики кнопок 35 | self.setting.pushButton_7.clicked.connect(lambda: self.close()) 36 | self.setting.pushButton_6.clicked.connect(self.save_config) 37 | self.setting.pushButton_8.clicked.connect(self.load_image) 38 | self.setting.pushButton_9.clicked.connect(self.delete_image) 39 | self.setting.pushButton_12.clicked.connect(self.add_item) 40 | self.setting.pushButton_10.clicked.connect(self.apply_data) 41 | self.setting.pushButton_11.clicked.connect(self.del_row) 42 | 43 | # Подгружаем настройки если они уже имеются 44 | if os.path.exists(os.path.join("data", "config.json")): 45 | with open(os.path.join("data", "config.json")) as file: 46 | data = json.load(file) 47 | self.setting.lineEdit_4.setText(data['nick']) 48 | self.setting.lineEdit_2.setText(data['server_ip']) 49 | self.setting.lineEdit_3.setText(data['server_port']) 50 | 51 | 52 | # Перетаскивание безрамочного окна 53 | # ================================================================== 54 | def center(self): 55 | qr = self.frameGeometry() 56 | cp = QtWidgets.QDesktopWidget().availableGeometry().center() 57 | qr.moveCenter(cp) 58 | self.move(qr.topLeft()) 59 | 60 | def mousePressEvent(self, event): 61 | self.oldPos = event.globalPos() 62 | 63 | def mouseMoveEvent(self, event): 64 | try: 65 | delta = QtCore.QPoint(event.globalPos() - self.oldPos) 66 | self.move(self.x() + delta.x(), self.y() + delta.y()) 67 | self.oldPos = event.globalPos() 68 | except AttributeError: 69 | pass 70 | # ================================================================== 71 | 72 | 73 | # Загрузить изображение в качестве аватарки пользователя 74 | def load_image(self): 75 | image_path = QtWidgets.QFileDialog.getOpenFileName(filter="*.png\n*.jpg") 76 | 77 | if image_path[0]: # Если пользователь выбрал изображение 78 | if os.path.getsize(image_path[0]) < 5120: 79 | # В зависимости от формата изображения задаем имя 80 | image_format = "png" if "png" in image_path[1] else "jpg" 81 | shutil.copy(image_path[0], os.path.join("data", f"custom.{image_format}")) 82 | 83 | # Обновляем Label который отвечает за изображение 84 | image_pixmap = QtGui.QPixmap(os.path.join("data", f"custom.{image_format}")) 85 | self.setting.label.setPixmap(image_pixmap) 86 | else: 87 | message = "Изображение не должно быть больше 5кб" 88 | QtWidgets.QMessageBox.about(self, "Ошибка", message) 89 | 90 | 91 | # Инициализация параметров при загрузке окна с настройками 92 | def init_data(self): 93 | # Если установлена аватарка - добавляем в Label 94 | for filename in os.listdir("data"): 95 | if "custom" in filename: 96 | image_pixmap = QtGui.QPixmap(os.path.join("data", filename)) 97 | self.setting.label.setPixmap(image_pixmap) 98 | 99 | # Обновляем список серверов 100 | with open(os.path.join("data", "servers.json")) as file: 101 | server_list = json.load(file) 102 | 103 | # Бежим по ключам сервера, которые являются именем и заполняем таблицу 104 | for server in server_list: 105 | # Добавляем новую пустую строку 106 | rowPosition = self.setting.tableWidget.rowCount() 107 | self.setting.tableWidget.insertRow(rowPosition) 108 | 109 | # Заполняем её данными из конфига (имя, адрес, порт) 110 | self.setting.tableWidget.setItem(rowPosition, 0, QtWidgets.QTableWidgetItem(server)) 111 | self.setting.tableWidget.setItem(rowPosition, 1, QtWidgets.QTableWidgetItem(server_list[server]["ip"])) 112 | self.setting.tableWidget.setItem(rowPosition, 2, QtWidgets.QTableWidgetItem(server_list[server]["port"])) 113 | 114 | 115 | # Удалить аватарку 116 | def delete_image(self): 117 | for filename in os.listdir("data"): 118 | if "custom" in filename: 119 | os.remove(os.path.join("data", filename)) 120 | self.setting.label.setText("64x64") 121 | 122 | 123 | # Добавить новую строку в список с серверами 124 | def add_item(self): 125 | dataline = self.setting.lineEdit_5.text() 126 | 127 | # Если строка не пустая и содержит 3 элемента 128 | if dataline and len(dataline.split(":")) == 3: 129 | parsed = dataline.split(":") # [name, ip, port] 130 | 131 | # Проверяем данные на валидность 132 | if not (self.check_ip(parsed[1]) and self.check_port(parsed[2])): 133 | QtWidgets.QMessageBox.about(self, "Ошибка", "Проверьте правильность ввода данных!") 134 | return 135 | 136 | # Проверяем нет ли таких значений в таблице 137 | # Бежим по всем строкам таблицы 138 | for row in range(self.setting.tableWidget.rowCount()): 139 | name = self.setting.tableWidget.item(row, 0).text() 140 | ip = self.setting.tableWidget.item(row, 1).text() 141 | port = self.setting.tableWidget.item(row, 2).text() 142 | 143 | # Проверяем уникальность имени и связки ip & port 144 | if (parsed[0] == name) or (parsed[1] == ip and parsed[2] == port): 145 | QtWidgets.QMessageBox.about(self, "Ошибка", "Такие данные уже существуют!") 146 | return 147 | 148 | # Если совпадение не найдено добавляем в таблицу пустую строку 149 | rowPosition = self.setting.tableWidget.rowCount() 150 | self.setting.tableWidget.insertRow(rowPosition) 151 | 152 | # Заполняем пустую строку значениями 153 | for num, text in enumerate(parsed): 154 | self.setting.tableWidget.setItem(rowPosition, num, QtWidgets.QTableWidgetItem(text)) 155 | 156 | 157 | # Проверяем валидность ip адреса 158 | def check_ip(self, ip_line): 159 | regular_ip = "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" 160 | if not re.match(regular_ip, ip_line) is None: 161 | return True 162 | else: 163 | return False 164 | 165 | 166 | # Проверяем валидность порта 167 | def check_port(self, port_line): 168 | if port_line.isdecimal() and int(port_line) <= 65535: 169 | return True 170 | else: 171 | return False 172 | 173 | 174 | # Применить настройки из таблицы серверов 175 | def apply_data(self): 176 | row_index = self.setting.tableWidget.currentRow() # Индекс строки 177 | 178 | # Заменяем основные настройки на выбранные 179 | if row_index != -1: 180 | ip = self.setting.tableWidget.item(row_index, 1).text() 181 | port = self.setting.tableWidget.item(row_index, 2).text() 182 | self.setting.lineEdit_2.setText(ip) 183 | self.setting.lineEdit_3.setText(port) 184 | 185 | 186 | # Удалить строку из таблицы 187 | def del_row(self): 188 | row_index = self.setting.tableWidget.currentRow() # Индекс строки 189 | if row_index != -1: 190 | self.setting.tableWidget.removeRow(row_index) 191 | 192 | 193 | # Сохранить настройки пользователя 194 | def save_config(self): 195 | nick = self.setting.lineEdit_4.text() 196 | server_ip = self.setting.lineEdit_2.text() 197 | server_port = self.setting.lineEdit_3.text() 198 | 199 | # Обновляем датчики, для того чтобы пользователь видел какие поля правильные 200 | self.setting.lineEdit_2.setStyleSheet("border-radius: 7px;") 201 | self.setting.lineEdit_3.setStyleSheet("border-radius: 7px;") 202 | self.setting.lineEdit_4.setStyleSheet("border-radius: 7px;") 203 | 204 | # Проверяем корректность ввода пользователя 205 | if len(nick) >= 3 and len(nick) <= 20: 206 | if self.check_ip(server_ip): 207 | if self.check_port(server_port): 208 | # Перезаписываем конфиг с настройками пользователя 209 | with open(os.path.join("data", "config.json"), "w") as file: 210 | data = {"nick": nick, "server_ip": server_ip, "server_port": server_port} 211 | json.dump(data, file, indent=6) 212 | 213 | # Перезаписываем конфиг с настройками серверов 214 | all_server = {} 215 | for row in range(self.setting.tableWidget.rowCount()): 216 | name = self.setting.tableWidget.item(row, 0).text() 217 | ip = self.setting.tableWidget.item(row, 1).text() 218 | port = self.setting.tableWidget.item(row, 2).text() 219 | 220 | all_server[name] = { 221 | "ip": ip, 222 | "port": port 223 | } 224 | 225 | with open(os.path.join("data", "servers.json"), "w") as file: 226 | json.dump(all_server, file, indent=6) 227 | 228 | # Закрываем окно с настройками 229 | self.close() 230 | self.signal.emit(['update_config']) 231 | else: 232 | self.setting.lineEdit_3.setStyleSheet("border: 2px solid red; border-radius: 7px;") 233 | self.setting.lineEdit_3.setText("Проверьте правильность ввода SERVER_PORT") 234 | else: 235 | self.setting.lineEdit_2.setStyleSheet("border: 2px solid red; border-radius: 7px;") 236 | self.setting.lineEdit_2.setText("Проверьте правильность ввода SERVER_IP") 237 | else: 238 | self.setting.lineEdit_4.setStyleSheet("border: 2px solid red; border-radius: 7px;") 239 | self.setting.lineEdit_4.setText("Слишком длинный либо слишком короткий ник") 240 | -------------------------------------------------------------------------------- /methods/windows/settings.ui: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /methods/windows/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'settings.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.1 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(961, 350) 18 | self.frame = QtWidgets.QFrame(Form) 19 | self.frame.setGeometry(QtCore.QRect(10, 10, 941, 331)) 20 | self.frame.setStyleSheet("QFrame{\n" 21 | " border: 2px solid #434965;\n" 22 | " border-radius: 7px;\n" 23 | " background-color: #2A2F41;\n" 24 | "}") 25 | self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) 26 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 27 | self.frame.setObjectName("frame") 28 | self.lineEdit_2 = QtWidgets.QLineEdit(self.frame) 29 | self.lineEdit_2.setGeometry(QtCore.QRect(10, 180, 321, 41)) 30 | font = QtGui.QFont() 31 | font.setPointSize(11) 32 | font.setBold(True) 33 | font.setWeight(75) 34 | self.lineEdit_2.setFont(font) 35 | self.lineEdit_2.setFocusPolicy(QtCore.Qt.ClickFocus) 36 | self.lineEdit_2.setStyleSheet("QLineEdit{\n" 37 | " border-radius: 7px;\n" 38 | "}") 39 | self.lineEdit_2.setText("") 40 | self.lineEdit_2.setAlignment(QtCore.Qt.AlignCenter) 41 | self.lineEdit_2.setClearButtonEnabled(False) 42 | self.lineEdit_2.setObjectName("lineEdit_2") 43 | self.lineEdit_3 = QtWidgets.QLineEdit(self.frame) 44 | self.lineEdit_3.setGeometry(QtCore.QRect(10, 230, 321, 41)) 45 | font = QtGui.QFont() 46 | font.setPointSize(11) 47 | font.setBold(True) 48 | font.setWeight(75) 49 | self.lineEdit_3.setFont(font) 50 | self.lineEdit_3.setFocusPolicy(QtCore.Qt.ClickFocus) 51 | self.lineEdit_3.setStyleSheet("QLineEdit{\n" 52 | " border-radius: 7px;\n" 53 | "}") 54 | self.lineEdit_3.setText("") 55 | self.lineEdit_3.setAlignment(QtCore.Qt.AlignCenter) 56 | self.lineEdit_3.setClearButtonEnabled(False) 57 | self.lineEdit_3.setObjectName("lineEdit_3") 58 | self.pushButton_6 = QtWidgets.QPushButton(self.frame) 59 | self.pushButton_6.setGeometry(QtCore.QRect(150, 280, 181, 41)) 60 | font = QtGui.QFont() 61 | font.setPointSize(11) 62 | font.setBold(True) 63 | font.setWeight(75) 64 | self.pushButton_6.setFont(font) 65 | self.pushButton_6.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 66 | self.pushButton_6.setStyleSheet("QPushButton{\n" 67 | " color: white;\n" 68 | " border-radius: 7px;\n" 69 | " background-color: #595F76;\n" 70 | "}\n" 71 | "\n" 72 | "QPushButton:hover{\n" 73 | " background-color: #50566E;\n" 74 | "}\n" 75 | "\n" 76 | "QPushButton:pressed{\n" 77 | " background-color: #434965;\n" 78 | "}") 79 | self.pushButton_6.setObjectName("pushButton_6") 80 | self.pushButton_7 = QtWidgets.QPushButton(self.frame) 81 | self.pushButton_7.setGeometry(QtCore.QRect(10, 280, 131, 41)) 82 | font = QtGui.QFont() 83 | font.setPointSize(11) 84 | font.setBold(True) 85 | font.setWeight(75) 86 | self.pushButton_7.setFont(font) 87 | self.pushButton_7.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 88 | self.pushButton_7.setStyleSheet("QPushButton{\n" 89 | " color: white;\n" 90 | " border-radius: 7px;\n" 91 | " background-color: #595F76;\n" 92 | "}\n" 93 | "\n" 94 | "QPushButton:hover{\n" 95 | " background-color: #50566E;\n" 96 | "}\n" 97 | "\n" 98 | "QPushButton:pressed{\n" 99 | " background-color: #434965;\n" 100 | "}") 101 | self.pushButton_7.setObjectName("pushButton_7") 102 | self.lineEdit_4 = QtWidgets.QLineEdit(self.frame) 103 | self.lineEdit_4.setGeometry(QtCore.QRect(10, 130, 321, 41)) 104 | font = QtGui.QFont() 105 | font.setPointSize(11) 106 | font.setBold(True) 107 | font.setWeight(75) 108 | self.lineEdit_4.setFont(font) 109 | self.lineEdit_4.setFocusPolicy(QtCore.Qt.ClickFocus) 110 | self.lineEdit_4.setStyleSheet("QLineEdit{\n" 111 | " border-radius: 7px;\n" 112 | "}") 113 | self.lineEdit_4.setText("") 114 | self.lineEdit_4.setAlignment(QtCore.Qt.AlignCenter) 115 | self.lineEdit_4.setClearButtonEnabled(False) 116 | self.lineEdit_4.setObjectName("lineEdit_4") 117 | self.label = QtWidgets.QLabel(self.frame) 118 | self.label.setGeometry(QtCore.QRect(10, 10, 131, 111)) 119 | font = QtGui.QFont() 120 | font.setPointSize(11) 121 | font.setBold(True) 122 | font.setWeight(75) 123 | self.label.setFont(font) 124 | self.label.setStyleSheet("QLabel {\n" 125 | " color: white;\n" 126 | " border-radius: 0px;\n" 127 | " border: 2px solid #434965;;\n" 128 | "}") 129 | self.label.setAlignment(QtCore.Qt.AlignCenter) 130 | self.label.setObjectName("label") 131 | self.pushButton_8 = QtWidgets.QPushButton(self.frame) 132 | self.pushButton_8.setGeometry(QtCore.QRect(150, 20, 181, 41)) 133 | font = QtGui.QFont() 134 | font.setPointSize(11) 135 | font.setBold(True) 136 | font.setWeight(75) 137 | self.pushButton_8.setFont(font) 138 | self.pushButton_8.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 139 | self.pushButton_8.setStyleSheet("QPushButton{\n" 140 | " color: white;\n" 141 | " border-radius: 7px;\n" 142 | " background-color: #595F76;\n" 143 | "}\n" 144 | "\n" 145 | "QPushButton:hover{\n" 146 | " background-color: #50566E;\n" 147 | "}\n" 148 | "\n" 149 | "QPushButton:pressed{\n" 150 | " background-color: #434965;\n" 151 | "}") 152 | self.pushButton_8.setObjectName("pushButton_8") 153 | self.pushButton_9 = QtWidgets.QPushButton(self.frame) 154 | self.pushButton_9.setGeometry(QtCore.QRect(150, 70, 181, 41)) 155 | font = QtGui.QFont() 156 | font.setPointSize(11) 157 | font.setBold(True) 158 | font.setWeight(75) 159 | self.pushButton_9.setFont(font) 160 | self.pushButton_9.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 161 | self.pushButton_9.setStyleSheet("QPushButton{\n" 162 | " color: white;\n" 163 | " border-radius: 7px;\n" 164 | " background-color: #595F76;\n" 165 | "}\n" 166 | "\n" 167 | "QPushButton:hover{\n" 168 | " background-color: #50566E;\n" 169 | "}\n" 170 | "\n" 171 | "QPushButton:pressed{\n" 172 | " background-color: #434965;\n" 173 | "}") 174 | self.pushButton_9.setObjectName("pushButton_9") 175 | self.tableWidget = QtWidgets.QTableWidget(self.frame) 176 | self.tableWidget.setGeometry(QtCore.QRect(350, 60, 581, 211)) 177 | font = QtGui.QFont() 178 | font.setPointSize(11) 179 | font.setBold(True) 180 | font.setWeight(75) 181 | self.tableWidget.setFont(font) 182 | self.tableWidget.setStyleSheet("QTableWidget {\n" 183 | " color: white;\n" 184 | "}") 185 | self.tableWidget.setObjectName("tableWidget") 186 | self.tableWidget.setColumnCount(3) 187 | self.tableWidget.setRowCount(0) 188 | item = QtWidgets.QTableWidgetItem() 189 | item.setTextAlignment(QtCore.Qt.AlignCenter) 190 | font = QtGui.QFont() 191 | font.setPointSize(11) 192 | font.setBold(False) 193 | font.setWeight(50) 194 | font.setKerning(True) 195 | font.setStyleStrategy(QtGui.QFont.PreferDefault) 196 | item.setFont(font) 197 | self.tableWidget.setHorizontalHeaderItem(0, item) 198 | item = QtWidgets.QTableWidgetItem() 199 | item.setTextAlignment(QtCore.Qt.AlignCenter) 200 | font = QtGui.QFont() 201 | font.setPointSize(11) 202 | item.setFont(font) 203 | self.tableWidget.setHorizontalHeaderItem(1, item) 204 | item = QtWidgets.QTableWidgetItem() 205 | item.setTextAlignment(QtCore.Qt.AlignCenter) 206 | font = QtGui.QFont() 207 | font.setPointSize(11) 208 | item.setFont(font) 209 | self.tableWidget.setHorizontalHeaderItem(2, item) 210 | self.label_2 = QtWidgets.QLabel(self.frame) 211 | self.label_2.setGeometry(QtCore.QRect(350, 20, 581, 31)) 212 | font = QtGui.QFont() 213 | font.setPointSize(11) 214 | font.setBold(True) 215 | font.setWeight(75) 216 | self.label_2.setFont(font) 217 | self.label_2.setStyleSheet("QLabel {\n" 218 | " color: white;\n" 219 | " border: none;\n" 220 | " background-color: #595F76;\n" 221 | "}") 222 | self.label_2.setAlignment(QtCore.Qt.AlignCenter) 223 | self.label_2.setObjectName("label_2") 224 | self.pushButton_10 = QtWidgets.QPushButton(self.frame) 225 | self.pushButton_10.setGeometry(QtCore.QRect(800, 280, 131, 41)) 226 | font = QtGui.QFont() 227 | font.setPointSize(11) 228 | font.setBold(True) 229 | font.setWeight(75) 230 | self.pushButton_10.setFont(font) 231 | self.pushButton_10.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 232 | self.pushButton_10.setStyleSheet("QPushButton{\n" 233 | " color: white;\n" 234 | " border-radius: 7px;\n" 235 | " background-color: #595F76;\n" 236 | "}\n" 237 | "\n" 238 | "QPushButton:hover{\n" 239 | " background-color: #50566E;\n" 240 | "}\n" 241 | "\n" 242 | "QPushButton:pressed{\n" 243 | " background-color: #434965;\n" 244 | "}") 245 | self.pushButton_10.setObjectName("pushButton_10") 246 | self.lineEdit_5 = QtWidgets.QLineEdit(self.frame) 247 | self.lineEdit_5.setGeometry(QtCore.QRect(410, 280, 321, 41)) 248 | font = QtGui.QFont() 249 | font.setPointSize(11) 250 | font.setBold(True) 251 | font.setWeight(75) 252 | self.lineEdit_5.setFont(font) 253 | self.lineEdit_5.setFocusPolicy(QtCore.Qt.ClickFocus) 254 | self.lineEdit_5.setStyleSheet("QLineEdit{\n" 255 | " border-radius: 7px;\n" 256 | "}") 257 | self.lineEdit_5.setText("") 258 | self.lineEdit_5.setAlignment(QtCore.Qt.AlignCenter) 259 | self.lineEdit_5.setClearButtonEnabled(False) 260 | self.lineEdit_5.setObjectName("lineEdit_5") 261 | self.pushButton_11 = QtWidgets.QPushButton(self.frame) 262 | self.pushButton_11.setGeometry(QtCore.QRect(350, 280, 51, 41)) 263 | font = QtGui.QFont() 264 | font.setPointSize(11) 265 | font.setBold(True) 266 | font.setWeight(75) 267 | self.pushButton_11.setFont(font) 268 | self.pushButton_11.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 269 | self.pushButton_11.setStyleSheet("QPushButton{\n" 270 | " color: white;\n" 271 | " border-radius: 7px;\n" 272 | " background-color: #595F76;\n" 273 | "}\n" 274 | "\n" 275 | "QPushButton:hover{\n" 276 | " background-color: #50566E;\n" 277 | "}\n" 278 | "\n" 279 | "QPushButton:pressed{\n" 280 | " background-color: #434965;\n" 281 | "}") 282 | self.pushButton_11.setObjectName("pushButton_11") 283 | self.pushButton_12 = QtWidgets.QPushButton(self.frame) 284 | self.pushButton_12.setGeometry(QtCore.QRect(740, 280, 51, 41)) 285 | font = QtGui.QFont() 286 | font.setPointSize(14) 287 | font.setBold(True) 288 | font.setWeight(75) 289 | self.pushButton_12.setFont(font) 290 | self.pushButton_12.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 291 | self.pushButton_12.setStyleSheet("QPushButton{\n" 292 | " color: white;\n" 293 | " border-radius: 7px;\n" 294 | " background-color: #595F76;\n" 295 | "}\n" 296 | "\n" 297 | "QPushButton:hover{\n" 298 | " background-color: #50566E;\n" 299 | "}\n" 300 | "\n" 301 | "QPushButton:pressed{\n" 302 | " background-color: #434965;\n" 303 | "}") 304 | self.pushButton_12.setObjectName("pushButton_12") 305 | 306 | self.retranslateUi(Form) 307 | QtCore.QMetaObject.connectSlotsByName(Form) 308 | 309 | def retranslateUi(self, Form): 310 | _translate = QtCore.QCoreApplication.translate 311 | Form.setWindowTitle(_translate("Form", "Form")) 312 | self.lineEdit_2.setPlaceholderText(_translate("Form", "IP Сервера")) 313 | self.lineEdit_3.setPlaceholderText(_translate("Form", "PORT Сервера")) 314 | self.pushButton_6.setText(_translate("Form", "Сохранить и выйти")) 315 | self.pushButton_7.setText(_translate("Form", "Отменить")) 316 | self.lineEdit_4.setPlaceholderText(_translate("Form", "Никнейм")) 317 | self.label.setText(_translate("Form", "64x64")) 318 | self.pushButton_8.setText(_translate("Form", "Загрузить")) 319 | self.pushButton_9.setText(_translate("Form", "Удалить")) 320 | item = self.tableWidget.horizontalHeaderItem(0) 321 | item.setText(_translate("Form", "Название")) 322 | item = self.tableWidget.horizontalHeaderItem(1) 323 | item.setText(_translate("Form", "Адрес")) 324 | item = self.tableWidget.horizontalHeaderItem(2) 325 | item.setText(_translate("Form", "Порт")) 326 | self.label_2.setText(_translate("Form", "Список серверов")) 327 | self.pushButton_10.setText(_translate("Form", "Применить")) 328 | self.lineEdit_5.setPlaceholderText(_translate("Form", "Name:Ip:Port")) 329 | self.pushButton_11.setText(_translate("Form", "X")) 330 | self.pushButton_12.setText(_translate("Form", "+")) 331 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import json 5 | import socket 6 | from loguru import logger 7 | from PyQt5 import QtCore, QtGui, QtWidgets 8 | from des import * 9 | 10 | from methods.SettingsPanel import * 11 | from methods.ConnectThreadMonitor import * 12 | 13 | 14 | # Настраиваем глобальный логгер для вывода в stdout 15 | # ============================================================================================================ 16 | logger.remove() 17 | logger.add( 18 | sink=sys.stdout, 19 | colorize=True, 20 | format="{time:HH:mm:ss} - {name}:{function}:{line} - {message}", 21 | level="DEBUG" 22 | ) 23 | 24 | # Конфигурация логгера, который работает с журналом 25 | logger.add( 26 | sink=os.path.join("logs", "client_log.txt"), 27 | format="{time:HH:mm:ss} - {name}:{function}:{line} - {message}", 28 | level="INFO", 29 | rotation="100 MB", 30 | compression="zip", 31 | enqueue=True # Делаем логгер потокобезопасным 32 | ) 33 | # ============================================================================================================ 34 | 35 | 36 | # Интерфейс программы и обработчик событий внутри него 37 | class Client(QtWidgets.QMainWindow): 38 | def __init__(self, parent=None): 39 | QtWidgets.QWidget.__init__(self, parent) 40 | self.ui = Ui_MainWindow() 41 | self.ui.setupUi(self) 42 | 43 | # Данные из конфига (симметричный ключ получаем в ответе от сервера) 44 | self.nick = None 45 | self.ip = None 46 | self.port = None 47 | self.smile_type = None 48 | self.connect_status = False 49 | 50 | # Экземпляр класса для обработки соединений и сигналов 51 | self.connect_monitor = message_monitor() 52 | self.connect_monitor.mysignal.connect(self.signal_handler) 53 | 54 | # Отключаем стандартные границы окна программы 55 | self.setWindowFlag(QtCore.Qt.FramelessWindowHint) 56 | self.setAttribute(QtCore.Qt.WA_TranslucentBackground) 57 | self.center() 58 | 59 | # Блокируем кнопку "Выйти из чата" 60 | self.btn_locker(self.ui.pushButton_19, True) 61 | 62 | # Обработчики основных кнопок + кнопок с панели 63 | self.ui.pushButton.clicked.connect(self.send_message) 64 | self.ui.lineEdit.returnPressed.connect(self.send_message) 65 | self.ui.pushButton_2.clicked.connect(self.connect_to_server) 66 | self.ui.pushButton_3.clicked.connect(lambda: self.close()) 67 | self.ui.pushButton_4.clicked.connect(lambda: self.ui.listWidget.clear()) 68 | self.ui.pushButton_5.clicked.connect(lambda: self.showMinimized()) 69 | self.ui.pushButton_7.clicked.connect(self.setting_panel) 70 | self.ui.pushButton_19.clicked.connect(self.server_disconnect) 71 | 72 | # Обработчик смайликов 73 | self.ui.pushButton_10.clicked.connect(lambda: self.smile_send('1')) 74 | self.ui.pushButton_11.clicked.connect(lambda: self.smile_send('2')) 75 | self.ui.pushButton_12.clicked.connect(lambda: self.smile_send('3')) 76 | self.ui.pushButton_6.clicked.connect(lambda: self.smile_send('4')) 77 | self.ui.pushButton_8.clicked.connect(lambda: self.smile_send('5')) 78 | self.ui.pushButton_9.clicked.connect(lambda: self.smile_send('6')) 79 | self.ui.pushButton_13.clicked.connect(lambda: self.smile_send('7')) 80 | self.ui.pushButton_17.clicked.connect(lambda: self.smile_send('8')) 81 | self.ui.pushButton_16.clicked.connect(lambda: self.smile_send('9')) 82 | self.ui.pushButton_14.clicked.connect(lambda: self.smile_send('10')) 83 | self.ui.pushButton_15.clicked.connect(lambda: self.smile_send('11')) 84 | self.ui.pushButton_18.clicked.connect(lambda: self.smile_send('12')) 85 | self.ui.pushButton_21.clicked.connect(lambda: self.smile_send('13')) 86 | self.ui.pushButton_22.clicked.connect(lambda: self.smile_send('14')) 87 | self.ui.pushButton_24.clicked.connect(lambda: self.smile_send('15')) 88 | 89 | 90 | # Перетаскивание безрамочного окна 91 | # ================================================================== 92 | def center(self): 93 | qr = self.frameGeometry() 94 | cp = QtWidgets.QDesktopWidget().availableGeometry().center() 95 | qr.moveCenter(cp) 96 | self.move(qr.topLeft()) 97 | 98 | def mousePressEvent(self, event): 99 | self.oldPos = event.globalPos() 100 | 101 | def mouseMoveEvent(self, event): 102 | try: 103 | delta = QtCore.QPoint(event.globalPos() - self.oldPos) 104 | self.move(self.x() + delta.x(), self.y() + delta.y()) 105 | self.oldPos = event.globalPos() 106 | except AttributeError: 107 | pass 108 | # ================================================================== 109 | 110 | 111 | # Отправить смайлик 112 | def smile_send(self, smile_number: str): 113 | btn_base = {'1': self.ui.pushButton_10, 114 | '2': self.ui.pushButton_11, 115 | '3': self.ui.pushButton_12, 116 | '4': self.ui.pushButton_6, 117 | '5': self.ui.pushButton_8, 118 | '6': self.ui.pushButton_9, 119 | '7': self.ui.pushButton_13, 120 | '8': self.ui.pushButton_17, 121 | '9': self.ui.pushButton_16, 122 | '10': self.ui.pushButton_14, 123 | '11': self.ui.pushButton_15, 124 | '12': self.ui.pushButton_18, 125 | '13': self.ui.pushButton_21, 126 | '14': self.ui.pushButton_22, 127 | '15': self.ui.pushButton_24} 128 | 129 | change_style = """ 130 | border-radius: 35px; 131 | border: 2px solid white; 132 | """ 133 | 134 | default_style = """ 135 | border: none; 136 | """ 137 | 138 | if self.smile_type == None: 139 | btn_base[smile_number].setStyleSheet(change_style) 140 | self.smile_type = smile_number 141 | 142 | elif self.smile_type != None and self.smile_type != smile_number: 143 | btn_base[self.smile_type].setStyleSheet(default_style) 144 | btn_base[smile_number].setStyleSheet(change_style) 145 | self.smile_type = smile_number 146 | 147 | elif self.smile_type != None and self.smile_type == smile_number: 148 | btn_base[smile_number].setStyleSheet(default_style) 149 | self.smile_type = None 150 | 151 | logger.debug(f"SELF.SMILE_TYPE: {self.smile_type}") 152 | 153 | 154 | # Открыть окно для настройки клиента 155 | def setting_panel(self): 156 | setting_win = SettingPanel(self, self.connect_monitor.mysignal) 157 | setting_win.show() 158 | 159 | 160 | # Обновление конфигов клиента 161 | def update_config(self): 162 | """ 163 | Используется для обновления значений на лету, без необходимости 164 | перезапускать клиент (В случае если пользователь отредактировал настройки 165 | либо же запустил софт и необходимо проинициализировать значения) 166 | """ 167 | # Если конфиг уже был создан 168 | if os.path.exists(os.path.join("data", "config.json")): 169 | with open(os.path.join("data", "config.json")) as file: 170 | data = json.load(file) 171 | self.nick = data['nick'] 172 | self.ip = data['server_ip'] 173 | self.port = int(data['server_port']) 174 | 175 | 176 | # Обработчик сигналов из потока 177 | def signal_handler(self, value: list): 178 | # Обновление параметров конфига 179 | if value[0] == "update_config": 180 | self.update_config() 181 | 182 | # Обновление симметричного ключа 183 | elif value[0] == "SERVER_OK": 184 | self.connect_status = True 185 | item = QtWidgets.QListWidgetItem() 186 | item.setTextAlignment(QtCore.Qt.AlignHCenter) 187 | item.setText(f"SERVER: {value[1]}\n") 188 | self.ui.listWidget.addItem(item) 189 | logger.info(value) 190 | 191 | # Отправляем уведомление о том, что мы вступили в чат 192 | payload = ['USERS_NOTIFY', f"{self.nick} - Вступил в чат"] 193 | self.connect_monitor.send_encrypt(payload) 194 | 195 | # Если пользователь вступил или вышел из чата 196 | elif value[0] == "USERS_NOTIFY": 197 | item = QtWidgets.QListWidgetItem() 198 | item.setTextAlignment(QtCore.Qt.AlignHCenter) 199 | item.setText(f"SERVER: {value[1]}\n") 200 | self.ui.listWidget.addItem(item) 201 | logger.info(value) 202 | 203 | # Обработка сообщений других пользователей 204 | # ['ENCRYPT_MESSAGE', self.nick, avatar, message_text.encode()] 205 | elif value[0] == "ENCRYPT_MESSAGE": 206 | item = QtWidgets.QListWidgetItem() 207 | item.setTextAlignment(QtCore.Qt.AlignLeft) 208 | 209 | # Используем аватарку пользователя 210 | size = QtCore.QSize(45, 45) 211 | 212 | # Если отправили смайлик вместо аватарки 213 | if str(value[2]).isdecimal(): 214 | icon = QtGui.QIcon(os.path.join("icons", f"smile{value[2]}.png")) 215 | 216 | # Если отправлена аватарка 217 | elif isinstance(value[2], bytes): 218 | pixmap_obj = QtGui.QPixmap() 219 | pixmap_obj.loadFromData(value[2]) 220 | icon = QtGui.QIcon(pixmap_obj) 221 | 222 | # Если нет аватарки и не выбран смайлик 223 | else: 224 | icon = QtGui.QIcon(os.path.join("icons", "user.png")) 225 | 226 | # Задаем иконку в строку 227 | item.setIcon(icon) 228 | self.ui.listWidget.setIconSize(size) 229 | self.ui.listWidget.addItem(item) 230 | item.setText(f"{value[1]}:\n{value[-1]}") 231 | logger.info(value) 232 | 233 | elif value[0] == "CONNECTION_ERROR": 234 | message = "Сервер разорвал соединение\nповторите попытку через несколько минут" 235 | QtWidgets.QMessageBox.about(self, "Ошибка Соединения", message) 236 | self.btn_locker(self.ui.pushButton_2, False) 237 | self.btn_locker(self.ui.pushButton_7, False) 238 | self.btn_locker(self.ui.pushButton_19, True) 239 | self.connect_status = False 240 | logger.info(value) 241 | 242 | 243 | # Отправить сообщение на сервер 244 | def send_message(self): 245 | if self.connect_status: 246 | message_text = self.ui.lineEdit.text() 247 | profile_photo = None # Путь к фото пользователя 248 | 249 | # Если пользователь выбрал смайлик 250 | if self.smile_type: 251 | profile_photo = self.smile_type 252 | 253 | # Если не выбрал смайлик но есть аватарка 254 | elif os.path.exists(os.path.join("data", "custom.png")): 255 | with open(os.path.join("data", "custom.png"), "rb") as photo: 256 | profile_photo = photo.read() 257 | 258 | # Если нет аватарки и нет смайлика 259 | else: 260 | profile_photo = None 261 | 262 | # Если поле с текстом не пустое - шифруем сообщение и передаем на сервер 263 | if len(message_text) > 0: 264 | payload = ['ENCRYPT_MESSAGE', self.nick, profile_photo, message_text.encode()] 265 | self.connect_monitor.send_encrypt(payload) 266 | 267 | # Добавляем свое сообщение в ListWidget 268 | item = QtWidgets.QListWidgetItem() 269 | item.setTextAlignment(QtCore.Qt.AlignLeft) 270 | size = QtCore.QSize(45, 45) 271 | 272 | # Прикрепляем к нему нашу аватарку 273 | if self.smile_type: 274 | icon = QtGui.QIcon(os.path.join("icons", f"smile{self.smile_type}.png")) 275 | 276 | elif os.path.exists(os.path.join("data", "custom.png")): 277 | icon = QtGui.QIcon(os.path.join("data", "custom.png")) 278 | 279 | else: 280 | icon = QtGui.QIcon(os.path.join("icons", "user.png")) 281 | self.ui.listWidget.setIconSize(size) 282 | item.setIcon(icon) 283 | 284 | # Выводим свое сообщение в панель и удаляем с поля воода 285 | item.setText(f"{self.nick} (ВЫ):\n{message_text}") 286 | self.ui.listWidget.addItem(item) 287 | self.ui.lineEdit.clear() 288 | else: 289 | message = "Проверьте соединение с сервером" 290 | QtWidgets.QMessageBox.about(self, "Оповещение", message) 291 | 292 | 293 | # Покдлючаемся к общему серверу 294 | def connect_to_server(self): 295 | self.update_config() # Обновляем данные пользователя 296 | 297 | if self.nick != None: 298 | try: 299 | self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 300 | self.client.connect((self.ip, self.port)) 301 | 302 | # Запускаем мониторинг входящих сообщений 303 | self.connect_monitor.server_socket = self.client 304 | self.connect_monitor.start() 305 | 306 | # Блокируем кнопки и разблокируем кнопку "Выйти из чата" 307 | self.btn_locker(self.ui.pushButton_2, True) 308 | self.btn_locker(self.ui.pushButton_7, True) 309 | self.btn_locker(self.ui.pushButton_19, False) 310 | except Exception as err: 311 | message = "Ошибка соединения с сервером.\nПроверьте правильность ввода данных" 312 | QtWidgets.QMessageBox.about(self, "Оповещение", message) 313 | else: # Если пользователь не заполнил данные 314 | message = "Для начала заполните данные во вкладке 'Настройки'" 315 | QtWidgets.QMessageBox.about(self, "Оповещение", message) 316 | 317 | 318 | # Отключиться от сервера 319 | def server_disconnect(self): 320 | payload = ['EXIT', f'{self.nick}'] 321 | self.connect_monitor.send_encrypt(payload) 322 | self.client.close() 323 | self.ui.listWidget.clear() 324 | 325 | # Снимаем блокировку с основных кнопок и блокируем кнопку для отключения 326 | self.btn_locker(self.ui.pushButton_19, True) 327 | self.btn_locker(self.ui.pushButton_2, False) 328 | self.btn_locker(self.ui.pushButton_7, False) 329 | 330 | 331 | # Блокировщик кнопок 332 | def btn_locker(self, btn: object, lock_status: bool) -> None: 333 | default_style = """ 334 | QPushButton{ 335 | color: white; 336 | border-radius: 7px; 337 | background-color: #595F76; 338 | } 339 | QPushButton:hover{ 340 | background-color: #50566E; 341 | } 342 | QPushButton:pressed{ 343 | background-color: #434965; 344 | } 345 | """ 346 | 347 | lock_style = """ 348 | color: #9EA2AB; 349 | border-radius: 7px; 350 | background-color: #2C313C; 351 | """ 352 | 353 | if lock_status: 354 | btn.setDisabled(True) 355 | btn.setStyleSheet(lock_style) 356 | else: 357 | btn.setDisabled(False) 358 | btn.setStyleSheet(default_style) 359 | 360 | 361 | if __name__ == "__main__": 362 | app = QtWidgets.QApplication(sys.argv) 363 | myapp = Client() 364 | myapp.show() 365 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /des.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'untitled.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.1 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_MainWindow(object): 15 | def setupUi(self, MainWindow): 16 | MainWindow.setObjectName("MainWindow") 17 | MainWindow.resize(851, 521) 18 | self.centralwidget = QtWidgets.QWidget(MainWindow) 19 | self.centralwidget.setObjectName("centralwidget") 20 | self.frame = QtWidgets.QFrame(self.centralwidget) 21 | self.frame.setGeometry(QtCore.QRect(10, 10, 831, 501)) 22 | self.frame.setStyleSheet("QFrame{\n" 23 | " border-radius: 7px;\n" 24 | " background-color: #1B1D23;\n" 25 | "}") 26 | self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) 27 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 28 | self.frame.setObjectName("frame") 29 | self.lineEdit = QtWidgets.QLineEdit(self.frame) 30 | self.lineEdit.setGeometry(QtCore.QRect(290, 350, 481, 41)) 31 | font = QtGui.QFont() 32 | font.setPointSize(11) 33 | font.setBold(True) 34 | font.setWeight(75) 35 | self.lineEdit.setFont(font) 36 | self.lineEdit.setStyleSheet("QLineEdit{\n" 37 | " border-radius: 7px;\n" 38 | "}") 39 | self.lineEdit.setText("") 40 | self.lineEdit.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) 41 | self.lineEdit.setPlaceholderText("") 42 | self.lineEdit.setClearButtonEnabled(True) 43 | self.lineEdit.setObjectName("lineEdit") 44 | self.pushButton = QtWidgets.QPushButton(self.frame) 45 | self.pushButton.setGeometry(QtCore.QRect(780, 350, 41, 41)) 46 | font = QtGui.QFont() 47 | font.setPointSize(11) 48 | font.setBold(True) 49 | font.setWeight(75) 50 | self.pushButton.setFont(font) 51 | self.pushButton.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 52 | self.pushButton.setStyleSheet("QPushButton{\n" 53 | " color: white;\n" 54 | " border-radius: 7px;\n" 55 | " background-color: #595F76;\n" 56 | "}\n" 57 | "\n" 58 | "QPushButton:hover{\n" 59 | " background-color: #50566E;\n" 60 | "}\n" 61 | "\n" 62 | "QPushButton:pressed{\n" 63 | " background-color: #434965;\n" 64 | "}") 65 | self.pushButton.setText("") 66 | icon = QtGui.QIcon() 67 | icon.addPixmap(QtGui.QPixmap("icons/send.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 68 | self.pushButton.setIcon(icon) 69 | self.pushButton.setIconSize(QtCore.QSize(32, 32)) 70 | self.pushButton.setObjectName("pushButton") 71 | self.pushButton_2 = QtWidgets.QPushButton(self.frame) 72 | self.pushButton_2.setGeometry(QtCore.QRect(290, 400, 261, 41)) 73 | font = QtGui.QFont() 74 | font.setPointSize(11) 75 | font.setBold(True) 76 | font.setWeight(75) 77 | self.pushButton_2.setFont(font) 78 | self.pushButton_2.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 79 | self.pushButton_2.setStyleSheet("QPushButton{\n" 80 | " color: white;\n" 81 | " border-radius: 7px;\n" 82 | " background-color: #595F76;\n" 83 | "}\n" 84 | "\n" 85 | "QPushButton:hover{\n" 86 | " background-color: #50566E;\n" 87 | "}\n" 88 | "\n" 89 | "QPushButton:pressed{\n" 90 | " background-color: #434965;\n" 91 | "}") 92 | self.pushButton_2.setObjectName("pushButton_2") 93 | self.pushButton_4 = QtWidgets.QPushButton(self.frame) 94 | self.pushButton_4.setGeometry(QtCore.QRect(560, 450, 261, 41)) 95 | font = QtGui.QFont() 96 | font.setPointSize(11) 97 | font.setBold(True) 98 | font.setWeight(75) 99 | self.pushButton_4.setFont(font) 100 | self.pushButton_4.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 101 | self.pushButton_4.setStyleSheet("QPushButton{\n" 102 | " color: white;\n" 103 | " border-radius: 7px;\n" 104 | " background-color: #595F76;\n" 105 | "}\n" 106 | "\n" 107 | "QPushButton:hover{\n" 108 | " background-color: #50566E;\n" 109 | "}\n" 110 | "\n" 111 | "QPushButton:pressed{\n" 112 | " background-color: #434965;\n" 113 | "}") 114 | self.pushButton_4.setObjectName("pushButton_4") 115 | self.frame_3 = QtWidgets.QFrame(self.frame) 116 | self.frame_3.setGeometry(QtCore.QRect(-1, 0, 831, 31)) 117 | self.frame_3.setStyleSheet("QFrame{\n" 118 | " border-bottom-left-radius: 0px;\n" 119 | " border-bottom-right-radius: 0px;\n" 120 | " background-color: #2C313C;\n" 121 | "}") 122 | self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel) 123 | self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised) 124 | self.frame_3.setObjectName("frame_3") 125 | self.pushButton_3 = QtWidgets.QPushButton(self.frame_3) 126 | self.pushButton_3.setGeometry(QtCore.QRect(790, 0, 41, 31)) 127 | font = QtGui.QFont() 128 | font.setPointSize(11) 129 | font.setBold(True) 130 | font.setWeight(75) 131 | self.pushButton_3.setFont(font) 132 | self.pushButton_3.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 133 | self.pushButton_3.setStyleSheet("QPushButton{\n" 134 | " color: white;\n" 135 | " border: none;\n" 136 | " border-top-right-radius: 7px;\n" 137 | " background-color: #2C313C;\n" 138 | "}\n" 139 | "\n" 140 | "QPushButton:hover{\n" 141 | " background-color: #45494D;\n" 142 | "}\n" 143 | "\n" 144 | "QPushButton:pressed{\n" 145 | " color: #EA2F4E;\n" 146 | "}") 147 | self.pushButton_3.setObjectName("pushButton_3") 148 | self.pushButton_5 = QtWidgets.QPushButton(self.frame_3) 149 | self.pushButton_5.setGeometry(QtCore.QRect(750, 0, 41, 31)) 150 | font = QtGui.QFont() 151 | font.setPointSize(10) 152 | font.setBold(True) 153 | font.setWeight(75) 154 | self.pushButton_5.setFont(font) 155 | self.pushButton_5.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 156 | self.pushButton_5.setStyleSheet("QPushButton{\n" 157 | " color: white;\n" 158 | " border: none;\n" 159 | " border-top-right-radius: 7px;\n" 160 | " background-color: #2C313C;\n" 161 | "}\n" 162 | "\n" 163 | "QPushButton:hover{\n" 164 | " background-color: #45494D;\n" 165 | "}\n" 166 | "\n" 167 | "QPushButton:pressed{\n" 168 | " color: #EA2F4E;\n" 169 | "}") 170 | self.pushButton_5.setDefault(False) 171 | self.pushButton_5.setObjectName("pushButton_5") 172 | self.frame_4 = QtWidgets.QFrame(self.frame) 173 | self.frame_4.setGeometry(QtCore.QRect(10, 40, 271, 451)) 174 | self.frame_4.setStyleSheet("QFrame{\n" 175 | " color: white;\n" 176 | " border-radius: 7px;\n" 177 | " background-color: #2C313C;\n" 178 | "}") 179 | self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel) 180 | self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised) 181 | self.frame_4.setObjectName("frame_4") 182 | self.pushButton_6 = QtWidgets.QPushButton(self.frame_4) 183 | self.pushButton_6.setGeometry(QtCore.QRect(10, 130, 71, 71)) 184 | self.pushButton_6.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 185 | self.pushButton_6.setStyleSheet("QPushButton{\n" 186 | " border: none;\n" 187 | "}") 188 | self.pushButton_6.setText("") 189 | icon1 = QtGui.QIcon() 190 | icon1.addPixmap(QtGui.QPixmap("icons/smile4.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 191 | self.pushButton_6.setIcon(icon1) 192 | self.pushButton_6.setIconSize(QtCore.QSize(64, 64)) 193 | self.pushButton_6.setObjectName("pushButton_6") 194 | self.pushButton_8 = QtWidgets.QPushButton(self.frame_4) 195 | self.pushButton_8.setGeometry(QtCore.QRect(100, 130, 71, 71)) 196 | self.pushButton_8.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 197 | self.pushButton_8.setStyleSheet("QPushButton{\n" 198 | " border: none;\n" 199 | "}") 200 | self.pushButton_8.setText("") 201 | icon2 = QtGui.QIcon() 202 | icon2.addPixmap(QtGui.QPixmap("icons/smile5.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 203 | self.pushButton_8.setIcon(icon2) 204 | self.pushButton_8.setIconSize(QtCore.QSize(64, 64)) 205 | self.pushButton_8.setObjectName("pushButton_8") 206 | self.pushButton_9 = QtWidgets.QPushButton(self.frame_4) 207 | self.pushButton_9.setGeometry(QtCore.QRect(190, 130, 71, 71)) 208 | self.pushButton_9.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 209 | self.pushButton_9.setStyleSheet("QPushButton{\n" 210 | " border: none;\n" 211 | "}") 212 | self.pushButton_9.setText("") 213 | icon3 = QtGui.QIcon() 214 | icon3.addPixmap(QtGui.QPixmap("icons/smile6.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 215 | self.pushButton_9.setIcon(icon3) 216 | self.pushButton_9.setIconSize(QtCore.QSize(64, 64)) 217 | self.pushButton_9.setObjectName("pushButton_9") 218 | self.pushButton_10 = QtWidgets.QPushButton(self.frame_4) 219 | self.pushButton_10.setGeometry(QtCore.QRect(10, 50, 71, 71)) 220 | self.pushButton_10.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 221 | self.pushButton_10.setStyleSheet("QPushButton{\n" 222 | " border: none;\n" 223 | "}") 224 | self.pushButton_10.setText("") 225 | icon4 = QtGui.QIcon() 226 | icon4.addPixmap(QtGui.QPixmap("icons/smile1.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 227 | self.pushButton_10.setIcon(icon4) 228 | self.pushButton_10.setIconSize(QtCore.QSize(64, 64)) 229 | self.pushButton_10.setObjectName("pushButton_10") 230 | self.pushButton_11 = QtWidgets.QPushButton(self.frame_4) 231 | self.pushButton_11.setGeometry(QtCore.QRect(100, 50, 71, 71)) 232 | self.pushButton_11.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 233 | self.pushButton_11.setStyleSheet("QPushButton{\n" 234 | " border: none;\n" 235 | "}") 236 | self.pushButton_11.setText("") 237 | icon5 = QtGui.QIcon() 238 | icon5.addPixmap(QtGui.QPixmap("icons/smile2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 239 | self.pushButton_11.setIcon(icon5) 240 | self.pushButton_11.setIconSize(QtCore.QSize(64, 64)) 241 | self.pushButton_11.setObjectName("pushButton_11") 242 | self.pushButton_12 = QtWidgets.QPushButton(self.frame_4) 243 | self.pushButton_12.setGeometry(QtCore.QRect(190, 50, 71, 71)) 244 | self.pushButton_12.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 245 | self.pushButton_12.setStyleSheet("QPushButton{\n" 246 | " border: none;\n" 247 | "}") 248 | self.pushButton_12.setText("") 249 | icon6 = QtGui.QIcon() 250 | icon6.addPixmap(QtGui.QPixmap("icons/smile3.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 251 | self.pushButton_12.setIcon(icon6) 252 | self.pushButton_12.setIconSize(QtCore.QSize(64, 64)) 253 | self.pushButton_12.setObjectName("pushButton_12") 254 | self.pushButton_13 = QtWidgets.QPushButton(self.frame_4) 255 | self.pushButton_13.setGeometry(QtCore.QRect(10, 210, 71, 71)) 256 | self.pushButton_13.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 257 | self.pushButton_13.setStyleSheet("QPushButton{\n" 258 | " border: none;\n" 259 | "}") 260 | self.pushButton_13.setText("") 261 | icon7 = QtGui.QIcon() 262 | icon7.addPixmap(QtGui.QPixmap("icons/smile7.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 263 | self.pushButton_13.setIcon(icon7) 264 | self.pushButton_13.setIconSize(QtCore.QSize(64, 64)) 265 | self.pushButton_13.setObjectName("pushButton_13") 266 | self.pushButton_14 = QtWidgets.QPushButton(self.frame_4) 267 | self.pushButton_14.setGeometry(QtCore.QRect(10, 290, 71, 71)) 268 | self.pushButton_14.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 269 | self.pushButton_14.setStyleSheet("QPushButton{\n" 270 | " border: none;\n" 271 | "}") 272 | self.pushButton_14.setText("") 273 | icon8 = QtGui.QIcon() 274 | icon8.addPixmap(QtGui.QPixmap("icons/smile10.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 275 | self.pushButton_14.setIcon(icon8) 276 | self.pushButton_14.setIconSize(QtCore.QSize(64, 64)) 277 | self.pushButton_14.setObjectName("pushButton_14") 278 | self.pushButton_15 = QtWidgets.QPushButton(self.frame_4) 279 | self.pushButton_15.setGeometry(QtCore.QRect(100, 290, 71, 71)) 280 | self.pushButton_15.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 281 | self.pushButton_15.setStyleSheet("QPushButton{\n" 282 | " border: none;\n" 283 | "}") 284 | self.pushButton_15.setText("") 285 | icon9 = QtGui.QIcon() 286 | icon9.addPixmap(QtGui.QPixmap("icons/smile11.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 287 | self.pushButton_15.setIcon(icon9) 288 | self.pushButton_15.setIconSize(QtCore.QSize(64, 64)) 289 | self.pushButton_15.setObjectName("pushButton_15") 290 | self.pushButton_16 = QtWidgets.QPushButton(self.frame_4) 291 | self.pushButton_16.setGeometry(QtCore.QRect(190, 210, 71, 71)) 292 | self.pushButton_16.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 293 | self.pushButton_16.setStyleSheet("QPushButton{\n" 294 | " border: none;\n" 295 | "}") 296 | self.pushButton_16.setText("") 297 | icon10 = QtGui.QIcon() 298 | icon10.addPixmap(QtGui.QPixmap("icons/smile9.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 299 | self.pushButton_16.setIcon(icon10) 300 | self.pushButton_16.setIconSize(QtCore.QSize(64, 64)) 301 | self.pushButton_16.setObjectName("pushButton_16") 302 | self.pushButton_17 = QtWidgets.QPushButton(self.frame_4) 303 | self.pushButton_17.setGeometry(QtCore.QRect(100, 210, 71, 71)) 304 | self.pushButton_17.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 305 | self.pushButton_17.setStyleSheet("QPushButton{\n" 306 | " border: none;\n" 307 | "}") 308 | self.pushButton_17.setText("") 309 | icon11 = QtGui.QIcon() 310 | icon11.addPixmap(QtGui.QPixmap("icons/smile8.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 311 | self.pushButton_17.setIcon(icon11) 312 | self.pushButton_17.setIconSize(QtCore.QSize(64, 64)) 313 | self.pushButton_17.setObjectName("pushButton_17") 314 | self.pushButton_18 = QtWidgets.QPushButton(self.frame_4) 315 | self.pushButton_18.setGeometry(QtCore.QRect(190, 290, 71, 71)) 316 | self.pushButton_18.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 317 | self.pushButton_18.setStyleSheet("QPushButton{\n" 318 | " border: none;\n" 319 | "}") 320 | self.pushButton_18.setText("") 321 | icon12 = QtGui.QIcon() 322 | icon12.addPixmap(QtGui.QPixmap("icons/smile12.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 323 | self.pushButton_18.setIcon(icon12) 324 | self.pushButton_18.setIconSize(QtCore.QSize(64, 64)) 325 | self.pushButton_18.setObjectName("pushButton_18") 326 | self.pushButton_21 = QtWidgets.QPushButton(self.frame_4) 327 | self.pushButton_21.setGeometry(QtCore.QRect(10, 370, 71, 71)) 328 | self.pushButton_21.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 329 | self.pushButton_21.setStyleSheet("QPushButton{\n" 330 | " border: none;\n" 331 | "}") 332 | self.pushButton_21.setText("") 333 | icon13 = QtGui.QIcon() 334 | icon13.addPixmap(QtGui.QPixmap("icons/smile13.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 335 | self.pushButton_21.setIcon(icon13) 336 | self.pushButton_21.setIconSize(QtCore.QSize(64, 64)) 337 | self.pushButton_21.setObjectName("pushButton_21") 338 | self.pushButton_22 = QtWidgets.QPushButton(self.frame_4) 339 | self.pushButton_22.setGeometry(QtCore.QRect(100, 370, 71, 71)) 340 | self.pushButton_22.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 341 | self.pushButton_22.setStyleSheet("QPushButton{\n" 342 | " border: none;\n" 343 | "}") 344 | self.pushButton_22.setText("") 345 | icon14 = QtGui.QIcon() 346 | icon14.addPixmap(QtGui.QPixmap("icons/smile14.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 347 | self.pushButton_22.setIcon(icon14) 348 | self.pushButton_22.setIconSize(QtCore.QSize(64, 64)) 349 | self.pushButton_22.setObjectName("pushButton_22") 350 | self.pushButton_24 = QtWidgets.QPushButton(self.frame_4) 351 | self.pushButton_24.setGeometry(QtCore.QRect(190, 370, 71, 71)) 352 | self.pushButton_24.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 353 | self.pushButton_24.setStyleSheet("QPushButton{\n" 354 | " border: none;\n" 355 | "}") 356 | self.pushButton_24.setText("") 357 | icon15 = QtGui.QIcon() 358 | icon15.addPixmap(QtGui.QPixmap("icons/smile15.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 359 | self.pushButton_24.setIcon(icon15) 360 | self.pushButton_24.setIconSize(QtCore.QSize(64, 64)) 361 | self.pushButton_24.setObjectName("pushButton_24") 362 | self.label = QtWidgets.QLabel(self.frame_4) 363 | self.label.setGeometry(QtCore.QRect(0, 0, 271, 41)) 364 | font = QtGui.QFont() 365 | font.setPointSize(11) 366 | font.setBold(True) 367 | font.setWeight(75) 368 | self.label.setFont(font) 369 | self.label.setAlignment(QtCore.Qt.AlignCenter) 370 | self.label.setObjectName("label") 371 | self.pushButton_7 = QtWidgets.QPushButton(self.frame) 372 | self.pushButton_7.setGeometry(QtCore.QRect(290, 450, 261, 41)) 373 | font = QtGui.QFont() 374 | font.setPointSize(11) 375 | font.setBold(True) 376 | font.setWeight(75) 377 | self.pushButton_7.setFont(font) 378 | self.pushButton_7.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 379 | self.pushButton_7.setStyleSheet("QPushButton{\n" 380 | " color: white;\n" 381 | " border-radius: 7px;\n" 382 | " background-color: #595F76;\n" 383 | "}\n" 384 | "\n" 385 | "QPushButton:hover{\n" 386 | " background-color: #50566E;\n" 387 | "}\n" 388 | "\n" 389 | "QPushButton:pressed{\n" 390 | " background-color: #434965;\n" 391 | "}") 392 | self.pushButton_7.setObjectName("pushButton_7") 393 | self.listWidget = QtWidgets.QListWidget(self.frame) 394 | self.listWidget.setGeometry(QtCore.QRect(290, 41, 531, 301)) 395 | font = QtGui.QFont() 396 | font.setPointSize(11) 397 | font.setBold(True) 398 | font.setWeight(75) 399 | self.listWidget.setFont(font) 400 | self.listWidget.setTabletTracking(False) 401 | self.listWidget.setAutoFillBackground(False) 402 | self.listWidget.setStyleSheet("color: white;\n" 403 | "border-radius: 7px;\n" 404 | "background-color: #2C313C;\n" 405 | "") 406 | self.listWidget.setFrameShape(QtWidgets.QFrame.StyledPanel) 407 | self.listWidget.setFrameShadow(QtWidgets.QFrame.Sunken) 408 | self.listWidget.setLineWidth(1) 409 | self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) 410 | self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) 411 | self.listWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored) 412 | self.listWidget.setAutoScroll(True) 413 | self.listWidget.setTabKeyNavigation(False) 414 | self.listWidget.setProperty("showDropIndicator", True) 415 | self.listWidget.setDragDropOverwriteMode(False) 416 | self.listWidget.setAlternatingRowColors(False) 417 | self.listWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerItem) 418 | self.listWidget.setHorizontalScrollMode(QtWidgets.QAbstractItemView.ScrollPerItem) 419 | self.listWidget.setMovement(QtWidgets.QListView.Static) 420 | self.listWidget.setFlow(QtWidgets.QListView.TopToBottom) 421 | self.listWidget.setProperty("isWrapping", False) 422 | self.listWidget.setResizeMode(QtWidgets.QListView.Fixed) 423 | self.listWidget.setLayoutMode(QtWidgets.QListView.SinglePass) 424 | self.listWidget.setViewMode(QtWidgets.QListView.ListMode) 425 | self.listWidget.setUniformItemSizes(False) 426 | self.listWidget.setWordWrap(False) 427 | self.listWidget.setSelectionRectVisible(False) 428 | self.listWidget.setObjectName("listWidget") 429 | self.pushButton_19 = QtWidgets.QPushButton(self.frame) 430 | self.pushButton_19.setGeometry(QtCore.QRect(560, 400, 261, 41)) 431 | font = QtGui.QFont() 432 | font.setPointSize(11) 433 | font.setBold(True) 434 | font.setWeight(75) 435 | self.pushButton_19.setFont(font) 436 | self.pushButton_19.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 437 | self.pushButton_19.setStyleSheet("QPushButton{\n" 438 | " color: white;\n" 439 | " border-radius: 7px;\n" 440 | " background-color: #595F76;\n" 441 | "}\n" 442 | "\n" 443 | "QPushButton:hover{\n" 444 | " background-color: #50566E;\n" 445 | "}\n" 446 | "\n" 447 | "QPushButton:pressed{\n" 448 | " background-color: #434965;\n" 449 | "}") 450 | self.pushButton_19.setObjectName("pushButton_19") 451 | MainWindow.setCentralWidget(self.centralwidget) 452 | 453 | self.retranslateUi(MainWindow) 454 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 455 | 456 | def retranslateUi(self, MainWindow): 457 | _translate = QtCore.QCoreApplication.translate 458 | MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) 459 | self.pushButton_2.setText(_translate("MainWindow", "Подключиться")) 460 | self.pushButton_4.setText(_translate("MainWindow", "Очистить")) 461 | self.pushButton_3.setText(_translate("MainWindow", "X")) 462 | self.pushButton_5.setText(_translate("MainWindow", "_")) 463 | self.label.setText(_translate("MainWindow", "Emojis Icon Pack")) 464 | self.pushButton_7.setText(_translate("MainWindow", "Настройки")) 465 | self.listWidget.setSortingEnabled(False) 466 | self.pushButton_19.setText(_translate("MainWindow", "Выйти из чата")) 467 | -------------------------------------------------------------------------------- /untitled.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 851 10 | 521 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 10 21 | 10 22 | 831 23 | 501 24 | 25 | 26 | 27 | QFrame{ 28 | border-radius: 7px; 29 | background-color: #1B1D23; 30 | } 31 | 32 | 33 | QFrame::StyledPanel 34 | 35 | 36 | QFrame::Raised 37 | 38 | 39 | 40 | 41 | 290 42 | 350 43 | 481 44 | 41 45 | 46 | 47 | 48 | 49 | 11 50 | 75 51 | true 52 | 53 | 54 | 55 | QLineEdit{ 56 | border-radius: 7px; 57 | } 58 | 59 | 60 | 61 | 62 | 63 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 64 | 65 | 66 | 67 | 68 | 69 | true 70 | 71 | 72 | 73 | 74 | 75 | 780 76 | 350 77 | 41 78 | 41 79 | 80 | 81 | 82 | 83 | 11 84 | 75 85 | true 86 | 87 | 88 | 89 | PointingHandCursor 90 | 91 | 92 | QPushButton{ 93 | color: white; 94 | border-radius: 7px; 95 | background-color: #595F76; 96 | } 97 | 98 | QPushButton:hover{ 99 | background-color: #50566E; 100 | } 101 | 102 | QPushButton:pressed{ 103 | background-color: #434965; 104 | } 105 | 106 | 107 | 108 | 109 | 110 | 111 | icons/send.pngicons/send.png 112 | 113 | 114 | 115 | 32 116 | 32 117 | 118 | 119 | 120 | 121 | 122 | 123 | 290 124 | 400 125 | 261 126 | 41 127 | 128 | 129 | 130 | 131 | 11 132 | 75 133 | true 134 | 135 | 136 | 137 | PointingHandCursor 138 | 139 | 140 | QPushButton{ 141 | color: white; 142 | border-radius: 7px; 143 | background-color: #595F76; 144 | } 145 | 146 | QPushButton:hover{ 147 | background-color: #50566E; 148 | } 149 | 150 | QPushButton:pressed{ 151 | background-color: #434965; 152 | } 153 | 154 | 155 | Подключиться 156 | 157 | 158 | 159 | 160 | 161 | 560 162 | 450 163 | 261 164 | 41 165 | 166 | 167 | 168 | 169 | 11 170 | 75 171 | true 172 | 173 | 174 | 175 | PointingHandCursor 176 | 177 | 178 | QPushButton{ 179 | color: white; 180 | border-radius: 7px; 181 | background-color: #595F76; 182 | } 183 | 184 | QPushButton:hover{ 185 | background-color: #50566E; 186 | } 187 | 188 | QPushButton:pressed{ 189 | background-color: #434965; 190 | } 191 | 192 | 193 | Очистить 194 | 195 | 196 | 197 | 198 | 199 | -1 200 | 0 201 | 831 202 | 31 203 | 204 | 205 | 206 | QFrame{ 207 | border-bottom-left-radius: 0px; 208 | border-bottom-right-radius: 0px; 209 | background-color: #2C313C; 210 | } 211 | 212 | 213 | QFrame::StyledPanel 214 | 215 | 216 | QFrame::Raised 217 | 218 | 219 | 220 | 221 | 790 222 | 0 223 | 41 224 | 31 225 | 226 | 227 | 228 | 229 | 11 230 | 75 231 | true 232 | 233 | 234 | 235 | PointingHandCursor 236 | 237 | 238 | QPushButton{ 239 | color: white; 240 | border: none; 241 | border-top-right-radius: 7px; 242 | background-color: #2C313C; 243 | } 244 | 245 | QPushButton:hover{ 246 | background-color: #45494D; 247 | } 248 | 249 | QPushButton:pressed{ 250 | color: #EA2F4E; 251 | } 252 | 253 | 254 | X 255 | 256 | 257 | 258 | 259 | 260 | 750 261 | 0 262 | 41 263 | 31 264 | 265 | 266 | 267 | 268 | 10 269 | 75 270 | true 271 | 272 | 273 | 274 | PointingHandCursor 275 | 276 | 277 | QPushButton{ 278 | color: white; 279 | border: none; 280 | border-top-right-radius: 7px; 281 | background-color: #2C313C; 282 | } 283 | 284 | QPushButton:hover{ 285 | background-color: #45494D; 286 | } 287 | 288 | QPushButton:pressed{ 289 | color: #EA2F4E; 290 | } 291 | 292 | 293 | _ 294 | 295 | 296 | false 297 | 298 | 299 | 300 | 301 | 302 | 303 | 10 304 | 40 305 | 271 306 | 451 307 | 308 | 309 | 310 | QFrame{ 311 | color: white; 312 | border-radius: 7px; 313 | background-color: #2C313C; 314 | } 315 | 316 | 317 | QFrame::StyledPanel 318 | 319 | 320 | QFrame::Raised 321 | 322 | 323 | 324 | 325 | 10 326 | 130 327 | 71 328 | 71 329 | 330 | 331 | 332 | PointingHandCursor 333 | 334 | 335 | QPushButton{ 336 | border: none; 337 | } 338 | 339 | 340 | 341 | 342 | 343 | 344 | icons/smile4.pngicons/smile4.png 345 | 346 | 347 | 348 | 64 349 | 64 350 | 351 | 352 | 353 | 354 | 355 | 356 | 100 357 | 130 358 | 71 359 | 71 360 | 361 | 362 | 363 | PointingHandCursor 364 | 365 | 366 | QPushButton{ 367 | border: none; 368 | } 369 | 370 | 371 | 372 | 373 | 374 | 375 | icons/smile5.pngicons/smile5.png 376 | 377 | 378 | 379 | 64 380 | 64 381 | 382 | 383 | 384 | 385 | 386 | 387 | 190 388 | 130 389 | 71 390 | 71 391 | 392 | 393 | 394 | PointingHandCursor 395 | 396 | 397 | QPushButton{ 398 | border: none; 399 | } 400 | 401 | 402 | 403 | 404 | 405 | 406 | icons/smile6.pngicons/smile6.png 407 | 408 | 409 | 410 | 64 411 | 64 412 | 413 | 414 | 415 | 416 | 417 | 418 | 10 419 | 50 420 | 71 421 | 71 422 | 423 | 424 | 425 | PointingHandCursor 426 | 427 | 428 | QPushButton{ 429 | border: none; 430 | } 431 | 432 | 433 | 434 | 435 | 436 | 437 | icons/smile1.pngicons/smile1.png 438 | 439 | 440 | 441 | 64 442 | 64 443 | 444 | 445 | 446 | 447 | 448 | 449 | 100 450 | 50 451 | 71 452 | 71 453 | 454 | 455 | 456 | PointingHandCursor 457 | 458 | 459 | QPushButton{ 460 | border: none; 461 | } 462 | 463 | 464 | 465 | 466 | 467 | 468 | icons/smile2.pngicons/smile2.png 469 | 470 | 471 | 472 | 64 473 | 64 474 | 475 | 476 | 477 | 478 | 479 | 480 | 190 481 | 50 482 | 71 483 | 71 484 | 485 | 486 | 487 | PointingHandCursor 488 | 489 | 490 | QPushButton{ 491 | border: none; 492 | } 493 | 494 | 495 | 496 | 497 | 498 | 499 | icons/smile3.pngicons/smile3.png 500 | 501 | 502 | 503 | 64 504 | 64 505 | 506 | 507 | 508 | 509 | 510 | 511 | 10 512 | 210 513 | 71 514 | 71 515 | 516 | 517 | 518 | PointingHandCursor 519 | 520 | 521 | QPushButton{ 522 | border: none; 523 | } 524 | 525 | 526 | 527 | 528 | 529 | 530 | icons/smile7.pngicons/smile7.png 531 | 532 | 533 | 534 | 64 535 | 64 536 | 537 | 538 | 539 | 540 | 541 | 542 | 10 543 | 290 544 | 71 545 | 71 546 | 547 | 548 | 549 | PointingHandCursor 550 | 551 | 552 | QPushButton{ 553 | border: none; 554 | } 555 | 556 | 557 | 558 | 559 | 560 | 561 | icons/smile10.pngicons/smile10.png 562 | 563 | 564 | 565 | 64 566 | 64 567 | 568 | 569 | 570 | 571 | 572 | 573 | 100 574 | 290 575 | 71 576 | 71 577 | 578 | 579 | 580 | PointingHandCursor 581 | 582 | 583 | QPushButton{ 584 | border: none; 585 | } 586 | 587 | 588 | 589 | 590 | 591 | 592 | icons/smile11.pngicons/smile11.png 593 | 594 | 595 | 596 | 64 597 | 64 598 | 599 | 600 | 601 | 602 | 603 | 604 | 190 605 | 210 606 | 71 607 | 71 608 | 609 | 610 | 611 | PointingHandCursor 612 | 613 | 614 | QPushButton{ 615 | border: none; 616 | } 617 | 618 | 619 | 620 | 621 | 622 | 623 | icons/smile9.pngicons/smile9.png 624 | 625 | 626 | 627 | 64 628 | 64 629 | 630 | 631 | 632 | 633 | 634 | 635 | 100 636 | 210 637 | 71 638 | 71 639 | 640 | 641 | 642 | PointingHandCursor 643 | 644 | 645 | QPushButton{ 646 | border: none; 647 | } 648 | 649 | 650 | 651 | 652 | 653 | 654 | icons/smile8.pngicons/smile8.png 655 | 656 | 657 | 658 | 64 659 | 64 660 | 661 | 662 | 663 | 664 | 665 | 666 | 190 667 | 290 668 | 71 669 | 71 670 | 671 | 672 | 673 | PointingHandCursor 674 | 675 | 676 | QPushButton{ 677 | border: none; 678 | } 679 | 680 | 681 | 682 | 683 | 684 | 685 | icons/smile12.pngicons/smile12.png 686 | 687 | 688 | 689 | 64 690 | 64 691 | 692 | 693 | 694 | 695 | 696 | 697 | 10 698 | 370 699 | 71 700 | 71 701 | 702 | 703 | 704 | PointingHandCursor 705 | 706 | 707 | QPushButton{ 708 | border: none; 709 | } 710 | 711 | 712 | 713 | 714 | 715 | 716 | icons/smile13.pngicons/smile13.png 717 | 718 | 719 | 720 | 64 721 | 64 722 | 723 | 724 | 725 | 726 | 727 | 728 | 100 729 | 370 730 | 71 731 | 71 732 | 733 | 734 | 735 | PointingHandCursor 736 | 737 | 738 | QPushButton{ 739 | border: none; 740 | } 741 | 742 | 743 | 744 | 745 | 746 | 747 | icons/smile14.pngicons/smile14.png 748 | 749 | 750 | 751 | 64 752 | 64 753 | 754 | 755 | 756 | 757 | 758 | 759 | 190 760 | 370 761 | 71 762 | 71 763 | 764 | 765 | 766 | PointingHandCursor 767 | 768 | 769 | QPushButton{ 770 | border: none; 771 | } 772 | 773 | 774 | 775 | 776 | 777 | 778 | icons/smile15.pngicons/smile15.png 779 | 780 | 781 | 782 | 64 783 | 64 784 | 785 | 786 | 787 | 788 | 789 | 790 | 0 791 | 0 792 | 271 793 | 41 794 | 795 | 796 | 797 | 798 | 11 799 | 75 800 | true 801 | 802 | 803 | 804 | Emojis Icon Pack 805 | 806 | 807 | Qt::AlignCenter 808 | 809 | 810 | 811 | 812 | 813 | 814 | 290 815 | 450 816 | 261 817 | 41 818 | 819 | 820 | 821 | 822 | 11 823 | 75 824 | true 825 | 826 | 827 | 828 | PointingHandCursor 829 | 830 | 831 | QPushButton{ 832 | color: white; 833 | border-radius: 7px; 834 | background-color: #595F76; 835 | } 836 | 837 | QPushButton:hover{ 838 | background-color: #50566E; 839 | } 840 | 841 | QPushButton:pressed{ 842 | background-color: #434965; 843 | } 844 | 845 | 846 | Настройки 847 | 848 | 849 | 850 | 851 | 852 | 290 853 | 41 854 | 531 855 | 301 856 | 857 | 858 | 859 | 860 | 11 861 | 75 862 | true 863 | 864 | 865 | 866 | false 867 | 868 | 869 | false 870 | 871 | 872 | color: white; 873 | border-radius: 7px; 874 | background-color: #2C313C; 875 | 876 | 877 | 878 | QFrame::StyledPanel 879 | 880 | 881 | QFrame::Sunken 882 | 883 | 884 | 1 885 | 886 | 887 | Qt::ScrollBarAsNeeded 888 | 889 | 890 | Qt::ScrollBarAsNeeded 891 | 892 | 893 | QAbstractScrollArea::AdjustIgnored 894 | 895 | 896 | true 897 | 898 | 899 | false 900 | 901 | 902 | true 903 | 904 | 905 | false 906 | 907 | 908 | false 909 | 910 | 911 | QAbstractItemView::ScrollPerItem 912 | 913 | 914 | QAbstractItemView::ScrollPerItem 915 | 916 | 917 | QListView::Static 918 | 919 | 920 | QListView::TopToBottom 921 | 922 | 923 | false 924 | 925 | 926 | QListView::Fixed 927 | 928 | 929 | QListView::SinglePass 930 | 931 | 932 | QListView::ListMode 933 | 934 | 935 | false 936 | 937 | 938 | false 939 | 940 | 941 | false 942 | 943 | 944 | false 945 | 946 | 947 | 948 | 949 | 950 | 560 951 | 400 952 | 261 953 | 41 954 | 955 | 956 | 957 | 958 | 11 959 | 75 960 | true 961 | 962 | 963 | 964 | PointingHandCursor 965 | 966 | 967 | QPushButton{ 968 | color: white; 969 | border-radius: 7px; 970 | background-color: #595F76; 971 | } 972 | 973 | QPushButton:hover{ 974 | background-color: #50566E; 975 | } 976 | 977 | QPushButton:pressed{ 978 | background-color: #434965; 979 | } 980 | 981 | 982 | Выйти из чата 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | --------------------------------------------------------------------------------