chatserver/chatclient3.0.py
DZY 04373736da feat(py)
新聊天程序

Closes #1
2025-05-31 19:01:43 +08:00

312 lines
13 KiB
Python

import socket
import threading
import json
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
from datetime import datetime
from tkinter import font as tkfont
class ChatClient:
def __init__(self, root):
self.root = root
self.root.title("Python Chat")
self.root.geometry("800x600")
self.root.configure(bg='#f0f0f0')
self.current_user = None
self.current_chat = None
self.is_group_chat = False
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.setup_ui()
def setup_ui(self):
self.custom_font = tkfont.Font(family="Helvetica", size=10)
self.bold_font = tkfont.Font(family="Helvetica", size=10, weight="bold")
self.main_frame = ttk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.left_panel = ttk.Frame(self.main_frame, width=200)
self.left_panel.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
self.left_panel.pack_propagate(False)
self.right_panel = ttk.Frame(self.main_frame)
self.right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
self.login_frame = ttk.Frame(self.left_panel)
self.login_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(self.login_frame, text="Username:").pack(anchor=tk.W)
self.username_entry = ttk.Entry(self.login_frame)
self.username_entry.pack(fill=tk.X)
ttk.Label(self.login_frame, text="Password:").pack(anchor=tk.W, pady=(5, 0))
self.password_entry = ttk.Entry(self.login_frame, show="*")
self.password_entry.pack(fill=tk.X)
self.login_button = ttk.Button(self.login_frame, text="Login", command=self.login)
self.login_button.pack(fill=tk.X, pady=(5, 0))
self.register_button = ttk.Button(self.login_frame, text="Register", command=self.register)
self.register_button.pack(fill=tk.X, pady=(5, 0))
self.user_list_frame = ttk.LabelFrame(self.left_panel, text="Users")
self.user_list_frame.pack(fill=tk.BOTH, expand=True)
self.user_list = tk.Listbox(self.user_list_frame, font=self.custom_font, selectmode=tk.SINGLE)
self.user_list.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.user_list.bind('<<ListboxSelect>>', self.select_user)
self.group_list_frame = ttk.LabelFrame(self.left_panel, text="Groups")
self.group_list_frame.pack(fill=tk.BOTH, pady=(10, 0))
self.group_list = tk.Listbox(self.group_list_frame, font=self.custom_font, selectmode=tk.SINGLE)
self.group_list.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
self.group_list.bind('<<ListboxSelect>>', self.select_group)
self.create_group_button = ttk.Button(self.left_panel, text="Create Group", command=self.show_create_group_dialog)
self.create_group_button.pack(fill=tk.X, pady=(10, 0))
self.chat_frame = ttk.Frame(self.right_panel)
self.chat_frame.pack(fill=tk.BOTH, expand=True)
self.chat_header = ttk.Label(self.chat_frame, text="Select a chat", font=self.bold_font)
self.chat_header.pack(anchor=tk.W, padx=5, pady=5)
self.chat_display = scrolledtext.ScrolledText(
self.chat_frame, wrap=tk.WORD, state=tk.DISABLED,
font=self.custom_font, bg='white', padx=10, pady=10
)
self.chat_display.pack(fill=tk.BOTH, expand=True)
self.input_frame = ttk.Frame(self.right_panel)
self.input_frame.pack(fill=tk.X, pady=(10, 0))
self.message_entry = ttk.Entry(self.input_frame)
self.message_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
self.message_entry.bind('<Return>', self.send_message)
self.send_button = ttk.Button(self.input_frame, text="Send", command=self.send_message)
self.send_button.pack(side=tk.RIGHT)
self.update_ui_state(False)
def update_ui_state(self, logged_in):
state = tk.NORMAL if logged_in else tk.DISABLED
self.user_list.config(state=state)
self.group_list.config(state=state)
self.message_entry.config(state=state)
self.send_button.config(state=state)
self.create_group_button.config(state=state)
if not logged_in:
self.user_list.delete(0, tk.END)
self.group_list.delete(0, tk.END)
self.chat_header.config(text="Select a chat")
self.chat_display.config(state=tk.NORMAL)
self.chat_display.delete(1.0, tk.END)
self.chat_display.config(state=tk.DISABLED)
self.current_chat = None
def login(self):
username = self.username_entry.get()
password = self.password_entry.get()
if not username or not password:
messagebox.showerror("Error", "Username and password are required")
return
try:
self.socket.connect(('localhost', 5555))
self.socket.send(json.dumps({
'type': 'login',
'username': username,
'password': password
}).encode('utf-8'))
response = json.loads(self.socket.recv(1024).decode('utf-8'))
if response['type'] == 'login_success':
self.current_user = username
self.update_ui_state(True)
threading.Thread(target=self.receive_messages, daemon=True).start()
else:
messagebox.showerror("Error", "Login failed")
self.socket.close()
except Exception as e:
messagebox.showerror("Error", f"Connection error: {str(e)}")
def register(self):
username = self.username_entry.get()
password = self.password_entry.get()
if not username or not password:
messagebox.showerror("Error", "Username and password are required")
return
try:
temp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
temp_socket.connect(('localhost', 5555))
temp_socket.send(json.dumps({
'type': 'register',
'username': username,
'password': password
}).encode('utf-8'))
response = json.loads(temp_socket.recv(1024).decode('utf-8'))
if response['type'] == 'register_success':
messagebox.showinfo("Success", "Registration successful")
else:
messagebox.showerror("Error", "Registration failed - username may be taken")
temp_socket.close()
except Exception as e:
messagebox.showerror("Error", f"Connection error: {str(e)}")
def receive_messages(self):
while True:
try:
message = self.socket.recv(1024).decode('utf-8')
if not message:
break
data = json.loads(message)
if data['type'] == 'user_list':
self.user_list.delete(0, tk.END)
for user in data['users']:
if user != self.current_user:
self.user_list.insert(tk.END, user)
elif data['type'] == 'group_list':
self.group_list.delete(0, tk.END)
for group in data['groups']:
self.group_list.insert(tk.END, group)
elif data['type'] == 'message':
self.display_message(
data['sender'],
data['receiver'],
data['message'],
data['is_group']
)
elif data['type'] == 'initial_messages':
for msg in data['messages']:
self.display_message(
msg['sender'],
msg['receiver'],
msg['message'],
msg['is_group'],
msg['timestamp']
)
except Exception as e:
print(f"Error receiving message: {e}")
break
def display_message(self, sender, receiver, message, is_group, timestamp=None):
timestamp = timestamp or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if (is_group and receiver == self.current_chat and sender != self.current_user) or \
(not is_group and ((sender == self.current_chat and receiver == self.current_user) or \
(sender == self.current_user and receiver == self.current_chat))):
self.chat_display.config(state=tk.NORMAL)
if sender == self.current_user:
self.chat_display.tag_config('right', justify='right', foreground='blue')
self.chat_display.insert(tk.END, f"{message} ({timestamp})\n", 'right')
else:
sender_label = f"{sender} (group)" if is_group and sender != self.current_chat else sender
self.chat_display.tag_config('left', justify='left', foreground='green')
self.chat_display.insert(tk.END, f"{sender_label}: {message} ({timestamp})\n", 'left')
self.chat_display.config(state=tk.DISABLED)
self.chat_display.see(tk.END)
def select_user(self, event):
selection = event.widget.curselection()
if selection:
self.current_chat = event.widget.get(selection[0])
self.is_group_chat = False
self.chat_header.config(text=f"Chat with {self.current_chat}")
self.chat_display.config(state=tk.NORMAL)
self.chat_display.delete(1.0, tk.END)
self.chat_display.config(state=tk.DISABLED)
def select_group(self, event):
selection = event.widget.curselection()
if selection:
self.current_chat = event.widget.get(selection[0])
self.is_group_chat = True
self.chat_header.config(text=f"Group: {self.current_chat}")
self.chat_display.config(state=tk.NORMAL)
self.chat_display.delete(1.0, tk.END)
self.chat_display.config(state=tk.DISABLED)
def send_message(self, event=None):
if not self.current_chat or not self.message_entry.get():
return
message = self.message_entry.get()
self.socket.send(json.dumps({
'type': 'message',
'sender': self.current_user,
'receiver': self.current_chat,
'message': message,
'is_group': self.is_group_chat
}).encode('utf-8'))
self.display_message(
self.current_user,
self.current_chat,
message,
self.is_group_chat
)
self.message_entry.delete(0, tk.END)
def show_create_group_dialog(self):
dialog = tk.Toplevel(self.root)
dialog.title("Create Group")
dialog.geometry("300x300")
ttk.Label(dialog, text="Group Name:").pack(anchor=tk.W, padx=10, pady=(10, 0))
group_name_entry = ttk.Entry(dialog)
group_name_entry.pack(fill=tk.X, padx=10, pady=(0, 10))
ttk.Label(dialog, text="Select Members:").pack(anchor=tk.W, padx=10, pady=(10, 0))
members_frame = ttk.Frame(dialog)
members_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
members_list = tk.Listbox(members_frame, selectmode=tk.MULTIPLE)
members_list.pack(fill=tk.BOTH, expand=True)
for i in range(self.user_list.size()):
members_list.insert(tk.END, self.user_list.get(i))
def create_group():
group_name = group_name_entry.get()
selected_indices = members_list.curselection()
selected_members = [members_list.get(i) for i in selected_indices]
if not group_name or not selected_members:
messagebox.showerror("Error", "Group name and at least one member are required")
return
self.socket.send(json.dumps({
'type': 'create_group',
'group_name': group_name,
'members': selected_members + [self.current_user]
}).encode('utf-8'))
dialog.destroy()
button_frame = ttk.Frame(dialog)
button_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
ttk.Button(button_frame, text="Create", command=create_group).pack(side=tk.RIGHT)
ttk.Button(button_frame, text="Cancel", command=dialog.destroy).pack(side=tk.RIGHT, padx=(0, 5))
def on_closing(self):
if self.current_user:
self.socket.close()
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
client = ChatClient(root)
root.protocol("WM_DELETE_WINDOW", client.on_closing)
root.mainloop()