diff --git a/editor.py b/editor.py index 46b21be..e7ad5bd 100644 --- a/editor.py +++ b/editor.py @@ -2,26 +2,33 @@ import network import tkinter as tk from tkinter import ttk, filedialog, colorchooser import sys +import colorsys +import random + +def random_color(seed): + return "#"+"".join([ + hex(int(i*255))[2:].zfill(2) + for i in colorsys.hsv_to_rgb(random.Random(seed).uniform(0.0, 1.0), 0.8, 1) + ]) listen_addr = ("0.0.0.0", int(sys.argv[1])) -send_addr = ("192.168.1.255", int(sys.argv[2])) +send_addr = ("192.168.0.255", int(sys.argv[2])) sock = network.Sock() + waiting_for_text = True +color = "#000000" root = tk.Tk() - -def foo(): - print("foo") +root.title("Éditeur en réseau") root.option_add('*tearOff', False) menubar = tk.Menu(root) root['menu'] = menubar menu_file = tk.Menu(menubar) menu_edit = tk.Menu(menubar) -menubar.add_cascade(menu=menu_file, label='File') -menubar.add_cascade(menu=menu_edit, label='Edit') -menu_file.add_command(label='New', command=foo) +menubar.add_cascade(menu=menu_file, label='Fichier') +menubar.add_cascade(menu=menu_edit, label='Édition') area = tk.Text(root) area.grid(column=0, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) @@ -30,47 +37,79 @@ area_scrolly = ttk.Scrollbar(root, orient=tk.VERTICAL, command=area.yview) area_scrolly.grid(column=1, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) area.configure(yscrollcommand=area_scrolly.set) +area_net = tk.Text(root, width=21) +area_net["state"] = "disabled" +area_net.grid(column=3, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) + +area_net_scrolly = ttk.Scrollbar(root, orient=tk.VERTICAL, command=area_net.yview) +area_net_scrolly.grid(column=4, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) +area_net.configure(yscrollcommand=area_net_scrolly.set) + canvas = tk.Canvas(root, background='white') canvas.grid(column=2, row=1, sticky=(tk.N, tk.S, tk.E, tk.W)) canvas_last_x = 0 canvas_last_y = 0 +def on_save_bt(): + path = tk.filedialog.asksaveasfilename(filetypes=[("Fichier texte", ".txt"), ("Autre", ".*")]) + if path != "": + f = open(path, "w") + f.write(area.get("1.0", "end")) + f.close() + +def on_open_bt(): + path = tk.filedialog.askopenfilename(filetypes=[("Fichier texte", ".txt"), ("Autre", ".*")]) + if path != (): + f = open(path, "r") + if f: + area.replace("1.0", "end", f.read()) + f.close() + +def on_color_bt(): + global color + color = tk.colorchooser.askcolor(initialcolor=color)[1] + def canvas_save_pos(event): global canvas_last_x, canvas_last_y canvas_last_x = event.x canvas_last_y = event.y def canvas_move(event): - canvas.create_line(canvas_last_x, canvas_last_y, event.x, event.y) - sock.send({"type":"create_line", "x1":canvas_last_x, "y1":canvas_last_y, "x2":event.x, "y2":event.y}, send_addr) + global color + canvas.create_line(canvas_last_x, canvas_last_y, event.x, event.y, fill=color) + sock.send({ + "type":"create_line", + "x1":canvas_last_x, + "y1":canvas_last_y, + "x2":event.x, + "y2":event.y, + "color":color + }, send_addr) canvas_save_pos(event) -def canvas_click(event): - print(colorchooser.askcolor(initialcolor='black')) +menu_file.add_command(label='Ouvrir', command=on_open_bt) +menu_file.add_command(label='Enregistrer', command=on_save_bt) canvas.bind("", canvas_save_pos) canvas.bind("", canvas_move) -canvas.bind("", canvas_click) root.rowconfigure(1, weight=1) root.columnconfigure(0, weight=1) root.columnconfigure(2, weight=1) -def on_save_bt(): - path = tk.filedialog.asksaveasfilename(filetypes=[("Fichier texte", ".txt")]) - file = open(path, "w") - file.write(area.get("1.0", "end")) - toolbar = ttk.Frame(root) toolbar.grid(column=0, row=0, columnspan=2, sticky=(tk.N, tk.S, tk.E, tk.W)) save_bt = ttk.Button(toolbar, text="Enregistrer", command=on_save_bt) save_bt.grid(column=0, row=0, sticky=(tk.W)) -open_bt = ttk.Button(toolbar, text="Ouvrir") +open_bt = ttk.Button(toolbar, text="Ouvrir", command=on_open_bt) open_bt.grid(column=1, row=0, sticky=(tk.W)) +color_bt = ttk.Button(toolbar, text="Couleur", command=on_color_bt) +color_bt.grid(column=2, row=0, sticky=(tk.W)) + old_text = "" def diff(old, new): if old == new: @@ -101,46 +140,69 @@ def diff(old, new): def on_area_input(event): global old_text - #print(area.get("1.0", "end")) - print(event) - #sock.send({"type":"insert", "start":"1.0", "text":area.get("1.0", "end")}, send_addr) - #sock.send({"type":"replace", "start":"1.0", "end":"end", "text":area.get("1.0", "end")}, send_addr) d = diff(old_text, area.get("1.0","end")) if d == None: return sock.send(d, send_addr) old_text = area.get("1.0","end") - - #if event.keycode == 25: - # area.mark_set("insert", "1.0") area.bind("", on_area_input) area.bind("", on_area_input) -area.tag_configure("plop", background="#ffaaaa") +authors = {} + +def set_author(author): + author = author[0]+"."+str(author[1]) + if author in authors: + return author + author_color = random_color(author.encode()) + authors[author] = author_color + area.tag_configure(author, background=author_color) + area_net.tag_configure(author, background=author_color) + area_net["state"] = "normal" + area_net.insert("end", author+"\n", author) + area_net["state"] = "disabled" + return author def on_request(r): global waiting_for_text, old_text print(r) c, a = r + if type(c) != dict: + return + if "message" in c: + area.replace("1.0", "end", c["message"], set_author(a)) + waiting_for_text = False + old_text = area.get("1.0","end") + if not "type" in c: + return if c["type"] == "set": + if not "text" in c: + return if waiting_for_text: - area.replace("1.0", "end", c["text"]) + if c["text"] != "\n": + area.replace("1.0", "end", c["text"]) waiting_for_text = False old_text = area.get("1.0","end") elif c["type"] == "insert": - area.insert(c["start"], c["text"], "plop") + if not "text" in c or not "start" in c: + return + area.insert(c["start"], c["text"], set_author(a)) old_text = area.get("1.0","end") elif c["type"] == "replace": - area.replace(c["start"], c["end"], c["text"], "plop") + if not "text" in c or not "start" in c or not "end" in c: + return + area.replace(c["start"], c["end"], c["text"], set_author(a)) old_text = area.get("1.0","end") elif c["type"] == "get": sock.send({"type":"set", "text":area.get("1.0", "end")}, send_addr) elif c["type"] == "create_line": - canvas.create_line(c["x1"], c["y1"], c["x2"], c["y2"]) + if not "color" in c or not "x1" in c or not "y1" in c or not "x2" in c or not "y2" in c: + return + canvas.create_line(c["x1"], c["y1"], c["x2"], c["y2"], fill=c["color"]) sock.listen(listen_addr, on_request) -sock.send({"type":"get"}, send_addr) +#sock.send({"type":"get"}, send_addr) root.mainloop() diff --git a/network.py b/network.py index 91b1c7e..d4863ff 100644 --- a/network.py +++ b/network.py @@ -11,8 +11,10 @@ class Sock: self.queue = [] self.queue_lock = Lock() self.sock_thread = None + self.has_sent = False def send_raw(self, message, address): + self.has_sent = True if type(message) == str: message = message.encode() if NETTRACE: @@ -21,6 +23,7 @@ class Sock: # Envoie une requete def send(self, message, address): + self.has_sent = True message = json.dumps(message) if NETTRACE: print("Send to", tuple(address), ":", message, file=sys.stderr) @@ -30,6 +33,8 @@ class Sock: # Si la fonction callback est donnee, elle sera appelee avec les requetes recues. # La methode get ne fonctionnera pas si un callback est defini. def listen(self, address, callback=None, length=65535): + if self.has_sent: + print("Attention, listen ne fonctionne pas si send a ete appele avant.") self.sock_thread = SockThread(self, tuple(address), length, callback) self.sock_thread.setDaemon(True) self.sock_thread.start() diff --git a/tutorial.md b/tutorial.md index ea1b65d..5fd6619 100644 --- a/tutorial.md +++ b/tutorial.md @@ -1,6 +1,14 @@ # Tutoriel Tkinter -Pour aller plus loin : [TkDocs Tutorial](https://tkdocs.com/tutorial/index.html) +**Prérequis** : +Il faut être un peu à l'aise avec le Python, au moins les variables, fonctions, manipulation des chaînes de caractères. +Si ce n'est pas le cas, [France-IOI](https://www.france-ioi.org/algo/chapters.php) propose une introduction ludique au Python. + +Tkinter est une bibliothèque d'interface graphique, qui permet de créer des fenêtres sous Linux, Windows et Mac. +C'est une des plus simples à utiliser en Python. +Seulement elle est un peu vieille et trop peu intuitive, d'où ce tutoriel qui peut servir de mini-référence. + +Pour aller plus loin : [TkDocs Tutorial](https://tkdocs.com/tutorial/index.html) (en anglais) ## Code de base @@ -64,7 +72,8 @@ On peut appeler plusieurs fois ces fonctions pour choisir plusieurs lignes et co ## Widgets -Tous les widgets peuvent être positionnés avec `grid`, de la même manière que ci-dessus. +Tous les widgets doivent être positionnés avec `grid`, de la même manière que ci-dessus. +Sans ça, ils ne seront pas affichés ! ### Label @@ -128,21 +137,21 @@ text = tk.Text(root) Il y a plusieurs méthodes pour interagir avec le texte : ```python -print(area.get("1.0", "end")) # obtenir tout le texte -print(area.get("2.0", "8.0")) # le texte de la ligne 2 à la ligne 8 -area.replace("1.0", "end", "le nouveau texte") # remplacer du texte -area.insert("1.0", "le nouveau texte") # insérer du texte -area.insert("1.0 +42 chars", "le nouveau texte") -area.insert("end -3 lines", "le nouveau texte") +print(text.get("1.0", "end")) # obtenir tout le texte +print(text.get("2.0", "8.0")) # le texte de la ligne 2 à la ligne 8 +text.replace("1.0", "end", "le nouveau texte") # remplacer du texte +text.insert("1.0", "le nouveau texte") # insérer du texte +text.insert("1.0 +42 chars", "le nouveau texte") +text.insert("end -3 lines", "le nouveau texte") ``` Pour mettre en forme certaines parties du texte, on peut utiliser des tags : ```python -area.tag_configure("fond_rouge", background="#ffaaaa") -area.tag_configure("insistance", foreground="#008800", underline=True) +text.tag_configure("fond_rouge", background="#ffaaaa") +text.tag_configure("insistance", foreground="#008800", underline=True) -area.insert("1.0", "ce texte sera sur fond rouge", "fond_rouge") +text.insert("1.0", "ce texte sera sur fond rouge", "fond_rouge") text.tag_add("insistance", "1.3", "1.8") ``` @@ -220,7 +229,7 @@ def onrelease(event): print("On a frappé le clavier !") print(event) -area.bind("", onrelease) +text.bind("", onrelease) ``` On peut aussi utiliser les infos contenues dans l'événement, par exemple `event.keycode`.