|
6 | 6 | from ui.add_contact_prompt import AddContactPrompt |
7 | 7 | from ui.smp_setup_window import SMPSetupWindow |
8 | 8 | from ui.smp_question_window import SMPQuestionWindow |
| 9 | +from ui.contact_nickname_prompt import ContactNicknamePrompt |
9 | 10 | from logic.authentication import authenticate_account |
10 | | -from logic.storage import check_account_file, load_account_data |
| 11 | +from logic.storage import check_account_file, save_account_data, load_account_data |
11 | 12 | from logic.background_worker import background_worker |
12 | 13 | from logic.utils import thread_failsafe_wrapper |
13 | 14 | import tkinter as tk |
@@ -105,7 +106,14 @@ def poll_ui_queue(self): |
105 | 106 | logger.debug("Opening chat window for contact (%s) because a new message arrived", msg["contact_id"]) |
106 | 107 | self.chat_windows_store_tmp[msg["contact_id"]] = ChatWindow(self, msg["contact_id"], self.ui_queue) |
107 | 108 |
|
108 | | - self.chat_windows_store_tmp[msg["contact_id"]].append_message("Contact: " + msg["message"]) |
| 109 | + with self.user_data_lock: |
| 110 | + contact_nickname = self.user_data["contacts"][msg["contact_id"]]["nickname"] |
| 111 | + |
| 112 | + |
| 113 | + if not contact_nickname: |
| 114 | + contact_nickname = "Contact" |
| 115 | + |
| 116 | + self.chat_windows_store_tmp[msg["contact_id"]].append_message(contact_nickname + ": " + msg["message"], contact_nickname=contact_nickname) |
109 | 117 |
|
110 | 118 | elif msg["type"] == "chat_closed": |
111 | 119 | del self.chat_windows_store_tmp[msg["contact_id"]] |
@@ -163,7 +171,7 @@ def show_contacts(self): |
163 | 171 | cursor="hand2" |
164 | 172 | ) |
165 | 173 | username_label.pack(side="left") |
166 | | - username_label.bind("<Button-1>", lambda e: self.copy_to_clipboard(username)) |
| 174 | + username_label.bind("<Button-1>", lambda e: self.copy_to_clipboard(username, "Your User ID has been copied to clipboard.")) |
167 | 175 |
|
168 | 176 | header_frame = tk.Frame(self, bg="black") |
169 | 177 | header_frame.pack(pady=10) |
@@ -191,35 +199,117 @@ def show_contacts(self): |
191 | 199 | add_button.image = plus_icon # Prevents garbage collection |
192 | 200 | add_button.pack(side="left", padx=(5, 0)) |
193 | 201 |
|
194 | | - self.contact_frame = tk.Frame(self, bg="black") |
195 | | - self.contact_frame.pack(fill="both", expand=True) |
196 | 202 |
|
197 | 203 |
|
198 | | - # Insert our saved contacts |
199 | | - for contact_id in self.user_data["contacts"]: |
200 | | - self.new_contact(contact_id) |
| 204 | + canvas = tk.Canvas(self, bg="black", highlightthickness=0) |
| 205 | + scrollbar = tk.Scrollbar(self, orient="vertical", command=canvas.yview) |
| 206 | + self.contact_frame = tk.Frame(canvas, bg="black") |
| 207 | + |
| 208 | + self.contact_frame.bind( |
| 209 | + "<Configure>", |
| 210 | + lambda e: canvas.configure(scrollregion=canvas.bbox("all")) |
| 211 | + ) |
| 212 | + |
| 213 | + contact_window = canvas.create_window((0, 0), window=self.contact_frame, anchor="nw") |
| 214 | + |
| 215 | + canvas.configure(yscrollcommand=scrollbar.set) |
201 | 216 |
|
| 217 | + canvas.pack(side="left", fill="both", expand=True) |
| 218 | + scrollbar.pack(side="right", fill="y") |
| 219 | + |
| 220 | + canvas.bind_all("<MouseWheel>", lambda e: canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")) # Windows / Linux mouse scrolling support |
| 221 | + canvas.bind_all("<Button-4>", lambda e: canvas.yview_scroll(-1, "units")) # MacOS |
| 222 | + canvas.bind_all("<Button-5>", lambda e: canvas.yview_scroll(1, "units")) # MacOS again |
| 223 | + |
| 224 | + canvas_frame = canvas.create_window((0, 0), window=self.contact_frame, anchor="nw") |
| 225 | + |
| 226 | + canvas.bind("<Configure>", lambda e: canvas.itemconfig(canvas_frame, width=e.width)) |
| 227 | + |
| 228 | + |
| 229 | + # Draw our saved contacts |
| 230 | + self.draw_contact_list() |
202 | 231 |
|
203 | 232 | # We initialize the background worker thread and other hooks here to prevent race conditions |
204 | 233 | self.init_hooks_and_background_worker() |
205 | 234 |
|
| 235 | + def on_mousewheel(event): |
| 236 | + canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") |
| 237 | + |
| 238 | + |
206 | 239 | def new_contact(self, contact_id): |
207 | | - btn = tk.Button( |
| 240 | + with self.user_data_lock: |
| 241 | + contact_name = contact_id if not self.user_data["contacts"][contact_id]["nickname"] else self.user_data["contacts"][contact_id]["nickname"] |
| 242 | + contact_is_verified = self.user_data["contacts"][contact_id]["lt_sign_key_smp"]["verified"] |
| 243 | + |
| 244 | + button = tk.Button( |
208 | 245 | self.contact_frame, |
209 | | - text=contact_id, |
| 246 | + text=contact_name, |
210 | 247 | bg="gray15", |
211 | 248 | fg="white", |
212 | 249 | relief="flat", |
213 | 250 | anchor="w", |
214 | 251 | command=lambda: self.open_chat(contact_id) |
215 | 252 | ) |
216 | | - btn.pack(fill="x", padx=15, pady=5) |
| 253 | + button.pack(fill="x", padx=15, pady=5) |
| 254 | + |
| 255 | + context_menu = tk.Menu(self, tearoff=0) |
| 256 | + context_menu.add_command( |
| 257 | + label="Copy Contact ID", |
| 258 | + command=lambda: self.copy_to_clipboard(contact_id, "Contact ID has been copied to the clipboard") |
| 259 | + ) |
| 260 | + context_menu.add_separator() |
| 261 | + |
| 262 | + # If no nickname is set |
| 263 | + if (contact_name == contact_id): |
| 264 | + # We only allow setting nicknames after SMP verification succeeds |
| 265 | + if contact_is_verified: |
| 266 | + context_menu.add_command( |
| 267 | + label="Set nickname", |
| 268 | + command=lambda: self.change_contact_nickname(contact_id) |
| 269 | + ) |
| 270 | + else: |
| 271 | + context_menu.add_command( |
| 272 | + label="Change nickname", |
| 273 | + command=lambda: self.change_contact_nickname(contact_id) |
| 274 | + ) |
| 275 | + |
| 276 | + context_menu.add_separator() |
| 277 | + |
| 278 | + context_menu.add_command( |
| 279 | + label="Remove nickname", |
| 280 | + command=lambda: self.remove_contact_nickname(contact_id) |
| 281 | + ) |
| 282 | + |
| 283 | + button.bind("<Button-3>", lambda event: context_menu.tk_popup(event.x_root, event.y_root)) # Windows / Linux |
| 284 | + button.bind("<Button-2>", lambda event: context_menu.tk_popup(event.x_root, event.y_root)) # MacOS |
| 285 | + |
| 286 | + def change_contact_nickname(self, contact_id): |
| 287 | + ContactNicknamePrompt(self, contact_id) |
| 288 | + |
| 289 | + def remove_contact_nickname(self, contact_id): |
| 290 | + with self.user_data_lock: |
| 291 | + self.user_data["contacts"][contact_id]["nickname"] = None |
| 292 | + |
| 293 | + logger.info("Removed nickname for contact (%s)", contact_id) |
| 294 | + save_account_data(self.user_data, self.user_data_lock) |
| 295 | + self.draw_contact_list() |
| 296 | + |
| 297 | + def draw_contact_list(self): |
| 298 | + logger.debug("Redrawing the contact list") |
| 299 | + for widget in self.contact_frame.winfo_children(): |
| 300 | + widget.destroy() |
| 301 | + |
| 302 | + with self.user_data_lock: |
| 303 | + contact_ids = list(self.user_data["contacts"].keys()) |
| 304 | + |
| 305 | + for contact_id in self.user_data["contacts"]: |
| 306 | + self.new_contact(contact_id) |
217 | 307 |
|
218 | | - |
219 | | - def copy_to_clipboard(self, text): |
| 308 | + logger.debug("Redrew the contact list") |
| 309 | + def copy_to_clipboard(self, text, success_message): |
220 | 310 | self.clipboard_clear() |
221 | 311 | self.clipboard_append(text) |
222 | | - messagebox.showinfo("Copied", "Your User ID has been copied to clipboard.") |
| 312 | + messagebox.showinfo("Copied", success_message) |
223 | 313 |
|
224 | 314 | def open_chat(self, contact_id): |
225 | 315 | with self.user_data_lock: |
|
0 commit comments