import tkinter, tkinter.filedialog, tkinter.messagebox, tkinter.simpledialog import tempfile, json, datetime, re, sys class Text(tkinter.Text): """ Tkinter Text Widget Override che permette di evidenziare il contenuto tramite espressioni regolari. """ def __init__(self, *args, **kwargs): tkinter.Text.__init__(self, *args, **kwargs) def evidenzia(self, pattern, tag, inizio="1.0", fine="end", regex=True): """ Evidenzia il testo selezionato da "pattern" e gli assegna il tag "tag". Speficicare "inzio" e "fine" per ristringere il campo della ricerca e regex=False se non si utilizza un espressione regolare. """ inizio = self.index(inizio) fine = self.index(fine) self.mark_set("matchStart", inizio) self.mark_set("matchEnd", inizio) self.mark_set("searchLimit", fine) occorrenze = tkinter.IntVar() while True: indice = self.search( pattern, "matchEnd", "searchLimit", count=occorrenze, regexp=regex ) if indice == "": break self.mark_set("matchStart", indice) self.mark_set("matchEnd", "%s+%sc" % (indice, occorrenze.get())) self.tag_add(tag, "matchStart", "matchEnd") class Applicazione(tkinter.Frame): """ Classe dell'applicazione Inizializzarla con un finestra creata con Tk(). """ def __init__(self, finestra): tkinter.Frame.__init__(self, finestra) finestra.iconify() #Variabili self.temp = tempfile.TemporaryFile(mode="w+t") self.testo = tkinter.StringVar() self.file = "" self.opzionifile = { "parent": finestra, "filetypes": [("text files", ".txt")], "defaultextension": ".txt", "initialfile": "file.txt" } self.opzionichat = { "font": ("Monaco", 13), "borderwidth": 2, "highlightthickness": 0 } #Gestione delle preferenze try: self.preferenze = json.load(open("preferenze.json")) except FileNotFoundError: tkinter.messagebox.showwarning( "Dropchat", "File delle preferenze non trovato!", detail="Preferenze di default ricostruite." ) self.creapreferenze() #Gestione della finestra finestra.geometry(self.preferenze["Geometria"]) finestra.configure(background = "#a8a8a8") finestra.columnconfigure(1, weight=1) finestra.rowconfigure(0, weight=1) finestra.protocol('WM_DELETE_WINDOW', self.chiudi) finestra.deiconify() self.grid() #Inizzializzazione self.cambiatitolo() self.widgets() self.leggi() #Gestione delle ultime conversazioni try: self.cancellanontrovati() self.file = self.preferenze["Chat"][-1] except IndexError: risposta = tkinter.messagebox.askquestion( "Dropchat", "Nessuna conversazione trovata.", detail="Crearne una nuova ?" ) if risposta == "yes": self.nuovofile() #Loop principale self.loop() #Widget della finestra def widgets(self): """ Creare e disegna i widget della finestra. """ self.menubar = tkinter.Menu(finestra) self.chat = Text( finestra, width=100, height=30, relief="sunken", insertbackground="#fff", **self.opzionichat ) self.sidebar = tkinter.Listbox( finestra, width=20, borderwidth=0, background="#dce0e8" ) self.casella = tkinter.Entry( finestra, textvariable=self.testo, **self.opzionichat ) self.chat.grid(column=1, row=0, sticky="nswe", pady=(6, 3), padx=6) self.casella.grid(column=1, row=1, sticky="nswe", pady=(3, 6), padx=6) self.sidebar.grid(column=0, row=0, sticky="nswe", rowspan=2) #Bindings self.chat.bind("", "break") self.sidebar.bind("", self.cambiafile) self.sidebar.bind("", self.mostramenu) self.casella.bind("", self.scrivi) #Barra dei menu self.menubar = tkinter.Menu(self) menu = tkinter.Menu(self.menubar) #Primo menu self.menubar.add_cascade(label = "Conversazione", menu=menu) menu.add_command(label="Nuovo file", command=self.nuovofile) menu.add_command(label="Apri file...", command=self.aprifile) #Secondo menu menu = tkinter.Menu(self.menubar) self.menubar.add_cascade(label="Profilo", menu=menu) menu.add_command( label="Nome utente", command=lambda: self.modificapreferenza( "Utente", tkinter.simpledialog.askstring( "Nome Utente", "Scrivi il nome:", initialvalue=self.preferenze["Utente"], parent=finestra ) ) ) menu.add_command( label="Chiave", command=lambda: self.modificapreferenza( "Chiave", tkinter.filedialog.askopenfilename( title="Scegli un file...", message="Apri una chiave di decodifica.", **self.opzionifile ) ) ) #Terzo menu menu = tkinter.Menu(self.menubar) self.menubar.add_cascade(label="Modifica", menu = menu) menu.add_command( label="Taglia", command=lambda: finestra.focus_get().event_generate("<>") ) menu.add_command( label="Copia", command=lambda: finestra.focus_get().event_generate("<>") ) menu.add_command( label="Incolla", command=lambda: finestra.focus_get().event_generate("<>") ) finestra.config(menu=self.menubar) #Menu contestuale 1 self.menu1 = tkinter.Menu(self) self.menu1.add_command( label="Apri", command=self.cambiafile ) self.menu1.add_command(label="Cancella", command=self.cancellafile ) self.menu1.add_separator() self.menu1.add_command( label="Nuovo file", command=self.nuovofile ) self.menu1.add_command( label="Ricarica", command=lambda: self.sidebar.delete(0,"end") ) #Menu contestuale 2 self.menu2 = tkinter.Menu(self) self.menu2.add_command( label="Nuovo file", command=self.nuovofile ) self.menu2.add_command( label="Ricarica", command=lambda: self.sidebar.delete(0,"end") ) def leggi(self): """Legge il file corrente dal buffer e lo scrive nella chat""" self.aggiorna() self.temp.seek(0) self.chat.delete(0.0,"end") self.chat.insert("end", self.temp.read()) self.colora() def scrivi(self, event): """Scrive un messaggio nel file""" if self.file != "": with open(self.file, "a") as file: if self.testo.get() != "": riga = "[%s] %s: %s\n" % ( datetime.datetime.now().strftime("%d-%m-%y %H:%M"), self.preferenze["Utente"], self.testo.get() ) self.testo.set("") file.write(riga) def aggiorna(self): """Copia il file nel Buffer""" self.temp.seek(0) self.temp.truncate() if self.file != "": with open(self.file) as testo: self.temp.write(testo.read()) def cambiatitolo(self): """ Aggiorna il titolo della finestra in modo che corrisponda al file correntemente aperto. """ file = re.search("([^/]*)$", self.file) if self.file == "": finestra.title("Dropchat") else: finestra.title("Dropchat - " + file.group(0)) def colora(self): """ Evidenzia parti semantiche nel testo della chat (Data, proprio nome utente e di altri). """ self.chat.tag_configure("data", foreground="#005d8f") self.chat.tag_configure("nome", foreground="#648f00") self.chat.tag_configure("altronome", foreground="#de7a31") self.chat.evidenzia("\[\d+-\d+-\d+ \d+:\d+\]", "data") for nome in re.findall(" ([a-zA-Z]+): ", self.chat.get(0.0, "end")): if nome == self.preferenze["Utente"]: self.chat.evidenzia(nome, "nome") else: self.chat.evidenzia(nome, "altronome") def mostramenu(self, event): """ Gestisce l'apertura e il posizionamento dei menu contestuali della sidebar. """ self.sidebar.indice = self.sidebar.nearest(event.y) if self.sidebar.indice < 0: self.menu2.post(event.x_root, event.y_root) return self.sidebar.activate(self.sidebar.indice) _, offset, _, altezza = self.sidebar.bbox(self.sidebar.indice) if event.y > altezza + offset + self.sidebar.size(): self.menu2.post(event.x_root, event.y_root) else: self.menu1.post(event.x_root, event.y_root) def filerecenti(self): """Inserisce nella sidebar i file recentemente aperti""" for x, file in enumerate(self.preferenze["Chat"]): file = " - " + re.search("([^/]*)$", file).group(0) if file not in self.sidebar.get(x): self.sidebar.insert(x, file) def cancellafile(self): """Rimuove dalla sidebar il file correntemente selezionato""" if self.preferenze["Chat"][self.sidebar.indice] == self.file: self.file = "" del self.preferenze["Chat"][self.sidebar.indice] json.dump(self.preferenze, open("preferenze.json", "w")) self.leggi() self.cambiatitolo() def cambiafile(self, _ = None): """ Apre il file correntemente selezionato dalla sidebar nella chat. """ file = self.preferenze["Chat"][int(self.sidebar.curselection()[0])] if self.file != file: self.file = file self.leggi() self.cambiatitolo() def aprifile(self): """ Gestice la finestra di selezione di un nuovo file e lo apre nella chat. """ if sys.platform == "darwin": file = tkinter.filedialog.askopenfilename( title="Scegli un file...", message="Apri una conversazione esistente.", **self.opzionifile ) else: file = tkinter.filedialog.askopenfilename( title="Scegli un file...", **self.opzionifile ) if file not in self.preferenze["Chat"] and file != "": self.preferenze["Chat"] += file, json.dump(self.preferenze, open("preferenze.json", "w")) self.file = file self.leggi() self.cambiatitolo() def nuovofile(self): """ Gestisce la finestra per la creazione di un nuovo file e lo apre nella chat. """ if sys.platform == "darwin": file = tkinter.filedialog.asksaveasfilename( title="Crea un file...", message="Scegli il nome della nuova conversazione.", **self.opzionifile ) else: file = tkinter.filedialog.asksaveasfilename( title="Crea un file...", **self.opzionifile ) if file != "": self.file = file open(self.file,"w").close() if self.file not in self.preferenze["Chat"]: self.preferenze["Chat"] += self.file, json.dump(self.preferenze, open("preferenze.json", "w")) self.leggi() self.cambiatitolo() def cancellanontrovati(self): """ Rimuove i riferimenti dalla sidebar di tutti i file che non sono piĆ¹ disponibili. """ for x, file in enumerate(self.preferenze["Chat"]): try: open(file) self.file = file except FileNotFoundError: del self.preferenze["Chat"][x] continue json.dump(self.preferenze, open("preferenze.json", "w")) def creapreferenze(self): """ Ricrea le preferenze di default se non vengono trovate. """ default = { "Utente": "Nomeutente", "Chat": ["chat.txt"], "Chiave": "chiave.key", "Geometria": "800x500+500+250" } json.dump(default,open("preferenze.json", "w")) self.preferenze = json.load(open("preferenze.json")) def modificapreferenza(self, chiave, preferenza): """ Modifica una preferenza e la salva nel file esterno. """ if preferenza != "": self.preferenze[chiave] = preferenza json.dump(self.preferenze, open("preferenze.json", "w")) def chiudi(self): """ Salva la geometria della finestra per il prossimo avvio, chiude ed elimina la cache, chiude la finestra ed esce. """ self.modificapreferenza("Geometria", finestra.geometry()) self.temp.close() finestra.destroy() def loop(self): """ Loop principale Viene eseguito ogni 200 ms. Aggiorna i file recenti e il file aperto nella chat. """ self.filerecenti() self.leggi() self.after(200, self.loop) finestra = tkinter.Tk() app = Applicazione(finestra) app.mainloop()