chatserver/chatclient3.0.py
2025-05-31 19:28:43 +08:00

324 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 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
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 - Invalid username or password")
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'))
temp_socket.close()
if response['type'] == 'register_success':
messagebox.showinfo("Success", "Registration successful. Please login now.")
else:
messagebox.showerror("Error", "Registration failed - username may be taken or invalid")
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']
)
elif data['type'] == 'server_shutdown':
messagebox.showinfo("Server", "Server is shutting down")
self.on_closing()
break
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) 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):
if not self.current_user:
return
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 hasattr(self, 'socket') and self.current_user:
try:
self.socket.close()
except:
pass
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
client = ChatClient(root)
root.protocol("WM_DELETE_WINDOW", client.on_closing)
root.mainloop()