diff --git a/Dropchat.py b/Dropchat.py index 5e10ba9..3a674e7 100644 --- a/Dropchat.py +++ b/Dropchat.py @@ -1,59 +1,59 @@ -import tkinter, tkinter.filedialog, tkinter.messagebox, tkinter.simpledialog -import tempfile, json, datetime, re, sys - +import re +import sys +import tempfile +import json +import datetime +import tkinter +import tkinter.filedialog +import tkinter.messagebox +import tkinter.simpledialog class Text(tkinter.Text): """ Tkinter Text Widget - Override che permette di evidenziare il contenuto tramite - espressioni regolari. + Override that allows to highlight words with regex. """ def __init__(self, *args, **kwargs): tkinter.Text.__init__(self, *args, **kwargs) - def evidenzia(self, pattern, tag, inizio="1.0", fine="end", regex=True): + def highlight(self, pattern, tag, start="1.0", stop="end", regex=True): """ - Evidenzia il testo selezionato da "pattern" e gli assegna - il tag "tag". Specificare "inizio" e "fine" per ristringere - il campo della ricerca e regex=False se non si utilizza un - espressione regolare. + Highlight text matched by "pattern" and apply it the tag "tag" + Specify "start" and "stop" in order to restrict the search and + regex=False if pattern is not a regex. """ - 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() + start = self.index(start) + stop = self.index(stop) + self.mark_set("matchStart", start) + self.mark_set("matchEnd", start) + self.mark_set("searchLimit", stop) + occurrences = tkinter.IntVar() while True: - indice = self.search( + index = self.search( pattern, "matchEnd", "searchLimit", - count=occorrenze, + count=occurrences, regexp=regex ) - if indice == "": + if index == "": break - self.mark_set("matchStart", indice) - self.mark_set("matchEnd", "%s+%sc" % (indice, occorrenze.get())) + self.mark_set("matchStart", index) + self.mark_set("matchEnd", "%s+%sc" % (index, occurrences.get())) self.tag_add(tag, "matchStart", "matchEnd") +class Application(tkinter.Frame): + """Application class""" + def __init__(self): + tkinter.Frame.__init__(self, window) + window.iconify() -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 + #Vars self.temp = tempfile.TemporaryFile(mode="w+t") - self.testo = tkinter.StringVar() + self.text = tkinter.StringVar() self.file = "" self.opzionifile = { - "parent": finestra, + "parent": window, "filetypes": [("text files", ".txt")], "defaultextension": ".txt", "initialfile": "file.txt" @@ -64,367 +64,296 @@ class Applicazione(tkinter.Frame): "highlightthickness": 0 } - #Gestione delle preferenze + #pref management try: - self.preferenze = json.load(open("preferenze.json")) + self.prefs = json.load(open("prefs.json")) except FileNotFoundError: tkinter.messagebox.showwarning( "Dropchat", - "File delle preferenze non trovato!", - detail="Preferenze di default ricostruite." - ) - self.creapreferenze() + "Preferences file non found!", + detail="Default preferences rebuilt.") + self.build_prefs() - #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() + #Gestione della window + window.geometry(self.prefs["geometry"]) + window.configure(background="#a8a8a8") + window.columnconfigure(1, weight=1) + window.rowconfigure(0, weight=1) + window.protocol('WM_DELETE_WINDOW', self.close) + window.deiconify() self.grid() #Inizzializzazione - self.cambiatitolo() + self.set_title() self.widgets() - self.leggi() + self.read() #Gestione delle ultime conversazioni try: - self.cancellanontrovati() - self.file = self.preferenze["Chat"][-1] + self.delete_not_found() + self.file = self.prefs["chat"][-1] except IndexError: - risposta = tkinter.messagebox.askquestion( + response = tkinter.messagebox.askquestion( "Dropchat", - "Nessuna conversazione trovata.", - detail="Crearne una nuova ?" - ) - if risposta == "yes": - self.nuovofile() + "No conversations found.", + detail="Make a new one?") + if response == "yes": + self.new_file() #Loop principale self.loop() - #Widget della finestra + #Widget della window def widgets(self): """ - Creare e disegna i widgets della finestra. + Draw window widgets. """ - self.menubar = tkinter.Menu(finestra) - self.chat = Text( - finestra, - width=100, - height=30, + self.menubar = tkinter.Menu(window) + self.chat = Text(window, + width=100, height=30, relief="sunken", insertbackground="#fff", - **self.opzionichat - ) + **self.opzionichat) self.sidebar = tkinter.Listbox( - finestra, - width=20, + window, width=20, borderwidth=0, - background="#dce0e8" - ) - self.casella = tkinter.Entry( - finestra, - textvariable=self.testo, - **self.opzionichat - ) + background="#dce0e8") + self.textbox = tkinter.Entry( + window, textvariable=self.text, + **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.textbox.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) + self.sidebar.bind("", self.switch_file) + self.sidebar.bind("", self.show_menu) + self.textbox.bind("", self.write) - #Barra dei menu + #Menu bar 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) + #Menu 1 + self.menubar.add_cascade(label="Conversation", menu=menu) + menu.add_command(label="New file", command=self.new_file) + menu.add_command(label="Open file...", command=self.open_file) - #Secondo menu + #Menu 2 menu = tkinter.Menu(self.menubar) - self.menubar.add_cascade(label="Profilo", menu=menu) + self.menubar.add_cascade(label="Profile", menu=menu) menu.add_command( - label="Nome utente", - command=lambda: self.modificapreferenza( - "Utente", + label="User name", + command=lambda: self.edit_prefs( + "username", 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 - ) - ) - ) + "User name", + "Insert your name:", + initialvalue=self.prefs["username"], + parent=window))) - #Terzo menu + #Menu 3 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) + self.menubar.add_cascade(label="Edit", menu=menu) + menu.add_command(label="Cut", + command=lambda: window.focus_get().event_generate("<>")) + menu.add_command(label="Copy", + command=lambda: window.focus_get().event_generate("<>")) + menu.add_command(label="Paste", + command=lambda: window.focus_get().event_generate("<>")) + window.config(menu=self.menubar) - #Menu contestuale 1 + #Context menu 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_command(label="Open", command=self.switch_file) + self.menu1.add_command(label="Delete", command=self.delete_file) 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") - ) + self.menu1.add_command(label="New file", command=self.new_file) + self.menu1.add_command(label="Reload", + command=lambda: self.sidebar.delete(0, "end")) - #Menu contestuale 2 + #Contextmenu 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") - ) + self.menu2.add_command(label="New file", command=self.new_file) + self.menu2.add_command(label="Reload", + command=lambda: self.sidebar.delete(0, "end")) - def leggi(self): - """Legge il file corrente dal buffer e lo scrive nella chat""" - self.aggiorna() + def read(self): + """ + Reads the current file from the buffer + and writes it into the chat. + """ + self.update() self.temp.seek(0) self.chat.delete(0.0, "end") self.chat.insert("end", self.temp.read()) - self.colora() + self.highlight() - def scrivi(self, event): - """Scrive un messaggio nel file""" + def write(self, event): + """Write a message in the current file""" if self.file != "": with open(self.file, "a") as file: - if self.testo.get() != "": - riga = "[%s] %s: %s\n" % ( + if self.text.get() != "": + row = "[%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) + self.prefs["username"], + self.text.get()) + self.text.set("") + file.write(row) - def aggiorna(self): - """Copia il file nel Buffer""" + def update(self): + """Copy file into the buffer""" self.temp.seek(0) self.temp.truncate() if self.file != "": - with open(self.file) as testo: - self.temp.write(testo.read()) + with open(self.file) as text: + self.temp.write(text.read()) - def cambiatitolo(self): - """ - Aggiorna il titolo della finestra in modo che corrisponda - al file correntemente aperto. - """ + def set_title(self): + """Update window's title to match current conversation""" file = re.search("([^/]*)$", self.file) if self.file == "": - finestra.title("Dropchat") + window.title("Dropchat") else: - finestra.title("Dropchat - " + file.group(0)) + window.title("Dropchat - " + file.group(0)) - def colora(self): + def highlight(self): """ - Evidenzia parti semantiche nel testo della chat - (Data, proprio nome utente e di altri). + Highlight semantic parts in the text chat. + (Date, username and others) """ - 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") + self.chat.tag_configure("date", foreground="#005d8f") + self.chat.tag_configure("name", foreground="#648f00") + self.chat.tag_configure("other", foreground="#de7a31") + self.chat.highlight("\[\d+-\d+-\d+ \d+:\d+\]", "data") + for name in re.findall(" ([a-zA-Z]+): ", self.chat.get(0.0, "end")): + if name == self.prefs["username"]: + self.chat.highlight(name, "name") else: - self.chat.evidenzia(nome, "altronome") + self.chat.highlight(name, "other") - 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: + def show_menu(self, event): + """Manages the opening and positioning of context menu""" + self.sidebar.index = self.sidebar.nearest(event.y) + if self.sidebar.index < 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.sidebar.activate(self.sidebar.index) + _, offset, _, height = self.sidebar.bbox(self.sidebar.index) + if event.y > height + 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"]): + def recent_files(self): + """Inserts recently opened files into the sidebar""" + for x, file in enumerate(self.prefs["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: + def delete_file(self): + """Removes the currently selected file from the sidebar""" + if self.prefs["Chat"][self.sidebar.index] == self.file: self.file = "" - del self.preferenze["Chat"][self.sidebar.indice] - json.dump(self.preferenze, open("preferenze.json", "w")) - self.leggi() - self.cambiatitolo() + del self.prefs["Chat"][self.sidebar.index] + json.dump(self.prefs, open("prefs.json", "w")) + self.read() + self.set_title() - def cambiafile(self, _=None): - """ - Apre il file correntemente selezionato - dalla sidebar nella chat. - """ - file = self.preferenze["Chat"][int(self.sidebar.curselection()[0])] + def switch_file(self, _=None): + """Opens the currently selected file from the sidebar in the chat""" + file = self.prefs["chat"][int(self.sidebar.curselection()[0])] if self.file != file: self.file = file - self.leggi() - self.cambiatitolo() + self.read() + self.set_title() - def aprifile(self): - """ - Gestisce la finestra di selezione di un nuovo file - e lo apre nella chat. - """ + def open_file(self): + """Selects a new file and opens it in the chat.""" if sys.platform == "darwin": file = tkinter.filedialog.askopenfilename( - title="Scegli un file...", - message="Apri una conversazione esistente.", - **self.opzionifile - ) + title="Choose a file...", + message="Open a conversation.", + **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")) + title="Choose a file...", + **self.opzionifile) + if file not in self.prefs["Chat"] and file != "": + self.prefs["chat"] += file, + json.dump(self.prefs, open("prefs.json", "w")) self.file = file - self.leggi() - self.cambiatitolo() + self.read() + self.set_title() - def nuovofile(self): - """ - Gestisce la finestra per la creazione di un nuovo file - e lo apre nella chat. - """ + def new_file(self): + """Creates a new file and opens it in the chat""" if sys.platform == "darwin": file = tkinter.filedialog.asksaveasfilename( - title="Crea un file...", - message="Scegli il nome della nuova conversazione.", - **self.opzionifile - ) + title="Create a file...", + message="Select the name of the new conversation.", + **self.opzionifile) else: file = tkinter.filedialog.asksaveasfilename( - title="Crea un file...", - **self.opzionifile - ) + title="Creates a new 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() + if self.file not in self.prefs["chat"]: + self.prefs["chat"] += self.file, + json.dump(self.prefs, open("prefs.json", "w")) + self.read() + self.set_title() - 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"]): + def delete_not_found(self): + """Removes files that are no longer available""" + for x, file in enumerate(self.prefs["chat"]): try: open(file) self.file = file except FileNotFoundError: - del self.preferenze["Chat"][x] + del self.prefs["Chat"][x] continue - json.dump(self.preferenze, open("preferenze.json", "w")) + json.dump(self.prefs, open("prefs.json", "w")) - def creapreferenze(self): - """ - Ricrea le preferenze di default se non vengono trovate. - """ + def build_prefs(self): + """Rebuild default preferences""" default = { - "Utente": "Nomeutente", - "Chat": ["chat.txt"], - "Chiave": "chiave.key", - "Geometria": "800x500+500+250" + "username": "user", + "chat": ["chat.txt"], + "geometry": "800x500+500+250" } - json.dump(default, open("preferenze.json", "w")) - self.preferenze = json.load(open("preferenze.json")) + json.dump(default, open("prefs.json", "w")) + self.prefs = json.load(open("prefs.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 edit_prefs(self, key, pref): + """Changing a preference and saves it into an external file.""" + if pref != "": + self.prefs[key] = pref + json.dump(self.prefs, open("prefs.json", "w")) - def chiudi(self): + def close(self): """ - Salva la geometria della finestra per il prossimo avvio, - chiude ed elimina la cache, - chiude la finestra ed esce. + Save the geometry of the window for the next boot, + closes and deletes the cache, + closes the window and exits. """ - self.modificapreferenza("Geometria", finestra.geometry()) + self.edit_prefs("geometry", window.geometry()) self.temp.close() - finestra.destroy() + window.destroy() def loop(self): """ - Loop principale - Viene eseguito ogni 200 ms. - Aggiorna i file recenti e il file aperto nella chat. + Main loop + Update recent files and current covnersation. """ - self.filerecenti() - self.leggi() + self.recent_files() + self.read() self.after(200, self.loop) -finestra = tkinter.Tk() -app = Applicazione(finestra) +window = tkinter.Tk() +app = Application() app.mainloop() diff --git a/License.txt b/License.txt deleted file mode 100644 index 16f372a..0000000 --- a/License.txt +++ /dev/null @@ -1,10 +0,0 @@ - * Dropchat - * - * Chat tramite Dropbox in python 3. - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * @author Michele Guerini Rocco aka Rnhmjoj - * @since 2013 \ No newline at end of file diff --git a/README.md b/README.md index ff66651..fb294df 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,31 @@ -Dropchat -======== +# Dropchat -Chat tramite Dropbox in python 3. ---------------------------------- +## Chat with Dropbox. -### Informazioni -Un chat client per chattare tramite le cartelle condivise di Dropbox. -Realizzato usando python 3 e l'interfaccia Tk. -### Istruzioni -Primo utilizzo: +### Info +A chat client that uses shared folders to Dropbox. +Made using python 3 and Tk interface. -Dalla barra dei menu selezionare: -* Profilo -> "Nome Utente" ed immettere il proprio nome. -* Conversazione -> "Nuovo file" o "Apri file.." e scegliere un file di testo -Scrivere un messaggio nella casella di testo e dare invio per immetterlo nel file. -Una volta scritto il file viene sincronizzato con Dropbox in modo che altri possano rispondere. +### Instructions +First usage: -I file recenti vengono elencati nella barra laterale e l'ultimo utilizzato viene riaperto all'avvio. -Per cancellare definitivamente un file basta spostarlo nel cestino mentre per rimuovere il riferimento dall'applicazione fare click destro sul file nella barra laterale e selezionare "Cancella". +From the menu bar select: +* Profile -> "User Name" and enter your name. +* Conversation -> "New File" or "Open File .." and choose a text file +Write a message in the text box and press return to enter it in the file. +Once written, the file is synced with Dropbox so that others can respond. -### Conversione -È incluso un setup.py per convertire Dropchat.py in un binario tramite py2app o py2exe. -Utilizzo: +Recent files are listed in the sidebar and on the last used is reopened at startup. +To permanently delete a file, simply move it to the trash while to remove the reference from the right-click on the file in the sidebar and select "Delete". + +### Building +A setup is included to convert Dropchat into a binary using py2exe or py2app. +Usage: - python setup.py \ No newline at end of file + python setup.py build + +### License +Dual licensed under the MIT and GPL licenses: +http://www.opensource.org/licenses/mit-license.php +http://www.gnu.org/licenses/gpl.html \ No newline at end of file diff --git a/preferenze.json b/preferenze.json deleted file mode 100644 index 0c650de..0000000 --- a/preferenze.json +++ /dev/null @@ -1 +0,0 @@ -{"Utente": "Nomeutente", "Geometria": "800x500+538+299", "Chiave": "chiave.key", "Chat": []} \ No newline at end of file diff --git a/prefs.json b/prefs.json new file mode 100644 index 0000000..6856df0 --- /dev/null +++ b/prefs.json @@ -0,0 +1 @@ +{"username": null, "geometry": "871x526+487+291", "chat": ["/Users/Michele/Desktop/file.txt"]} \ No newline at end of file diff --git a/setup.py b/setup.py index 63e717c..3674d57 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,15 @@ -import sys, setuptools +import sys +import setuptools -opzioni = { +options = { "name": "Dropchat", "version": "1.0", - "description": "Chat tramite dropbox.", + "description": "Chat with dropbox.", "author": "Rnhmjoj", "author_email": "micheleguerinirocco@me.com", "license": "MIT-GPL", - "app": ["Dropchat.py"], - "data_files": ["preferenze.json"], + "app": ["dropchat.py"], + "data_files": ["prefs.json"], "options": {}, "setup_requires": [], } @@ -17,21 +18,21 @@ if sys.platform == "darwin": try: import py2app sys.argv += "py2app", - opzioni["options"] = {"py2app": {"argv_emulation": False}} - opzioni["setup_requires"] += "py2app", + options["options"] = {"py2app": {"argv_emulation": False}} + options["setup_requires"] += "py2app", except ImportError: - exit("py2app non installato.") + sys.exit("py2app not installed") elif sys.platform == "win32": try: import py2exe sys.argv += "py2exe", - opzioni["options"] = {"py2exe": {"bundle_files": 1, "compressed": True}} - opzioni["setup_requires"] += "py2exe", + options["options"] = {"py2exe": {"bundle_files": 1, "compressed": True}} + options["setup_requires"] += "py2exe", except ImportError: - exit("py2exe non installato.") + sys.exit("py2exe not installed") else: - exit("Sistema non supportato") + sys.exit("OS not supported") -setuptools.setup(**opzioni) +setuptools.setup(**options)