dropchat/Dropchat.py

360 lines
9.9 KiB
Python
Raw Normal View History

2014-01-17 15:27:08 +01:00
import re
import sys
import tempfile
import json
import datetime
import tkinter
import tkinter.filedialog
import tkinter.messagebox
import tkinter.simpledialog
2013-08-25 19:09:55 +02:00
2013-02-15 19:28:32 +01:00
class Text(tkinter.Text):
2014-05-31 00:31:27 +02:00
'''
Tkinter Text Widget
2014-01-17 15:27:08 +01:00
Override that allows to highlight words with regex.
2014-05-31 00:31:27 +02:00
'''
2013-02-15 19:28:32 +01:00
def __init__(self, *args, **kwargs):
tkinter.Text.__init__(self, *args, **kwargs)
2013-08-25 19:09:55 +02:00
2014-05-31 00:31:27 +02:00
def highlight(self, pattern, tag, start='1.0', stop='end', regex=True):
'''
Highlight text matched by 'pattern' and apply it the tag 'tag'
Specify 'start' and 'stop' in order to restrict the search and
2014-01-17 15:27:08 +01:00
regex=False if pattern is not a regex.
2014-05-31 00:31:27 +02:00
'''
2014-01-17 15:27:08 +01:00
start = self.index(start)
stop = self.index(stop)
2014-05-31 00:31:27 +02:00
self.mark_set('matchStart', start)
self.mark_set('matchEnd', start)
self.mark_set('searchLimit', stop)
2014-01-17 15:27:08 +01:00
occurrences = tkinter.IntVar()
2013-02-15 19:28:32 +01:00
while True:
2014-01-17 15:27:08 +01:00
index = self.search(
2013-08-25 19:09:55 +02:00
pattern,
2014-05-31 00:31:27 +02:00
'matchEnd',
'searchLimit',
2014-01-17 15:27:08 +01:00
count=occurrences,
regexp=regex
)
2014-05-31 00:31:27 +02:00
if index == '':
2013-02-15 19:28:32 +01:00
break
2014-05-31 00:31:27 +02:00
self.mark_set('matchStart', index)
self.mark_set('matchEnd', '%s+%sc' % (index, occurrences.get()))
self.tag_add(tag, 'matchStart', 'matchEnd')
2013-02-15 19:28:32 +01:00
2014-01-17 15:27:08 +01:00
class Application(tkinter.Frame):
2014-05-31 00:31:27 +02:00
'''Application class'''
2014-01-17 15:27:08 +01:00
def __init__(self):
tkinter.Frame.__init__(self, window)
window.iconify()
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
#Vars
2014-05-31 00:31:27 +02:00
self.temp = tempfile.TemporaryFile(mode='w+t')
2014-01-17 15:27:08 +01:00
self.text = tkinter.StringVar()
2014-05-31 00:31:27 +02:00
self.file = ''
2013-02-15 19:28:32 +01:00
self.opzionifile = {
2014-05-31 00:31:27 +02:00
'parent': window,
'filetypes': [('text files', '.txt')],
'defaultextension': '.txt',
'initialfile': 'file.txt'
2013-02-15 19:28:32 +01:00
}
self.opzionichat = {
2014-05-31 00:31:27 +02:00
'font': ('Monaco', 13),
'borderwidth': 2,
'highlightthickness': 0
2013-02-15 19:28:32 +01:00
}
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
#pref management
2013-02-15 19:28:32 +01:00
try:
2014-05-31 00:31:27 +02:00
self.prefs = json.load(open('prefs.json'))
2013-02-15 19:28:32 +01:00
except FileNotFoundError:
tkinter.messagebox.showwarning(
2014-05-31 00:31:27 +02:00
'Dropchat',
'Preferences file non found!',
detail='Default preferences rebuilt.')
2014-01-17 15:27:08 +01:00
self.build_prefs()
#Gestione della window
2014-05-31 00:31:27 +02:00
window.geometry(self.prefs['geometry'])
window.configure(background='#a8a8a8')
2014-01-17 15:27:08 +01:00
window.columnconfigure(1, weight=1)
window.rowconfigure(0, weight=1)
window.protocol('WM_DELETE_WINDOW', self.close)
window.deiconify()
2013-02-15 19:28:32 +01:00
self.grid()
#Inizzializzazione
2014-01-17 15:27:08 +01:00
self.set_title()
2013-02-15 19:28:32 +01:00
self.widgets()
2014-01-17 15:27:08 +01:00
self.read()
2013-08-25 19:09:55 +02:00
#Gestione delle ultime conversazioni
try:
2014-01-17 15:27:08 +01:00
self.delete_not_found()
2014-05-31 00:31:27 +02:00
self.file = self.prefs['chat'][-1]
except IndexError:
2014-01-17 15:27:08 +01:00
response = tkinter.messagebox.askquestion(
2014-05-31 00:31:27 +02:00
'Dropchat',
'No conversations found.',
detail='Make a new one?')
if response == 'yes':
2014-01-17 15:27:08 +01:00
self.new_file()
2013-08-25 19:09:55 +02:00
#Loop principale
2013-02-15 19:28:32 +01:00
self.loop()
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
#Widget della window
2013-02-15 19:28:32 +01:00
def widgets(self):
2014-05-31 00:31:27 +02:00
'''
2014-01-17 15:27:08 +01:00
Draw window widgets.
2014-05-31 00:31:27 +02:00
'''
2014-01-17 15:27:08 +01:00
self.menubar = tkinter.Menu(window)
self.chat = Text(window,
width=100, height=30,
2014-05-31 00:31:27 +02:00
relief='sunken',
insertbackground='#fff',
2014-01-17 15:27:08 +01:00
**self.opzionichat)
self.sidebar = tkinter.Listbox(
2014-01-17 15:27:08 +01:00
window, width=20,
borderwidth=0,
2014-05-31 00:31:27 +02:00
background='#dce0e8')
2014-01-17 15:27:08 +01:00
self.textbox = tkinter.Entry(
window, textvariable=self.text,
**self.opzionichat)
2014-05-31 00:31:27 +02:00
self.chat.grid(column=1, row=0, sticky='nswe', pady=(6, 3), 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)
2013-08-25 19:09:55 +02:00
2013-02-15 19:28:32 +01:00
#Bindings
2014-05-31 00:31:27 +02:00
self.chat.bind('<KeyPress>', 'break')
self.sidebar.bind('<Double-Button-1>', self.switch_file)
self.sidebar.bind('<Button-2>', self.show_menu)
self.textbox.bind('<Return>', self.write)
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
#Menu bar
2013-02-15 19:28:32 +01:00
self.menubar = tkinter.Menu(self)
menu = tkinter.Menu(self.menubar)
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
#Menu 1
2014-05-31 00:31:27 +02:00
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)
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
#Menu 2
2013-02-15 19:28:32 +01:00
menu = tkinter.Menu(self.menubar)
2014-05-31 00:31:27 +02:00
self.menubar.add_cascade(label='Profile', menu=menu)
menu.add_command(
2014-05-31 00:31:27 +02:00
label='User name',
2014-01-17 15:27:08 +01:00
command=lambda: self.edit_prefs(
2014-05-31 00:31:27 +02:00
'username',
tkinter.simpledialog.askstring(
2014-05-31 00:31:27 +02:00
'User name',
'Insert your name:',
initialvalue=self.prefs['username'],
2014-01-17 15:27:08 +01:00
parent=window)))
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
#Menu 3
2013-02-15 19:28:32 +01:00
menu = tkinter.Menu(self.menubar)
2014-05-31 00:31:27 +02:00
self.menubar.add_cascade(label='Edit', menu=menu)
menu.add_command(label='Cut',
command=lambda: window.focus_get().event_generate('<<Cut>>'))
menu.add_command(label='Copy',
command=lambda: window.focus_get().event_generate('<<Copy>>'))
menu.add_command(label='Paste',
command=lambda: window.focus_get().event_generate('<<Paste>>'))
2014-01-17 15:27:08 +01:00
window.config(menu=self.menubar)
#Context menu 1
2013-02-15 19:28:32 +01:00
self.menu1 = tkinter.Menu(self)
2014-05-31 00:31:27 +02:00
self.menu1.add_command(label='Open', command=self.switch_file)
self.menu1.add_command(label='Delete', command=self.delete_file)
2013-02-15 19:28:32 +01:00
self.menu1.add_separator()
2014-05-31 00:31:27 +02:00
self.menu1.add_command(label='New file', command=self.new_file)
self.menu1.add_command(label='Reload',
command=lambda: self.sidebar.delete(0, 'end'))
2014-01-17 15:27:08 +01:00
#Contextmenu 2
2013-08-25 19:09:55 +02:00
self.menu2 = tkinter.Menu(self)
2014-05-31 00:31:27 +02:00
self.menu2.add_command(label='New file', command=self.new_file)
self.menu2.add_command(label='Reload',
command=lambda: self.sidebar.delete(0, 'end'))
2014-01-17 15:27:08 +01:00
def read(self):
2014-05-31 00:31:27 +02:00
'''
Reads the current file from the buffer
2014-01-17 15:27:08 +01:00
and writes it into the chat.
2014-05-31 00:31:27 +02:00
'''
2014-01-17 15:27:08 +01:00
self.update()
2013-02-15 19:28:32 +01:00
self.temp.seek(0)
2014-05-31 00:31:27 +02:00
self.chat.delete(0.0, 'end')
self.chat.insert('end', self.temp.read())
2014-01-17 15:27:08 +01:00
self.highlight()
2013-08-25 19:09:55 +02:00
2014-05-31 00:31:27 +02:00
def write(self, _):
'''Write a message in the current file'''
if self.file != '':
with open(self.file, 'a') as file:
if self.text.get() != '':
row = '[%s] %s: %s\n' % (
datetime.datetime.now().strftime('%d-%m-%y %H:%M'),
self.prefs['username'],
2014-01-17 15:27:08 +01:00
self.text.get())
2014-05-31 00:31:27 +02:00
self.text.set('')
2014-01-17 15:27:08 +01:00
file.write(row)
def update(self):
2014-05-31 00:31:27 +02:00
'''Copy file into the buffer'''
self.temp.seek(0)
self.temp.truncate()
2014-05-31 00:31:27 +02:00
if self.file != '':
2014-01-17 15:27:08 +01:00
with open(self.file) as text:
self.temp.write(text.read())
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def set_title(self):
2014-05-31 00:31:27 +02:00
'''Update window's title to match current conversation'''
file = re.search('([^/]*)$', self.file)
if self.file == '':
window.title('Dropchat')
2013-02-15 19:28:32 +01:00
else:
2014-05-31 00:31:27 +02:00
window.title('Dropchat - ' + file.group(0))
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def highlight(self):
2014-05-31 00:31:27 +02:00
'''
2014-01-17 15:27:08 +01:00
Highlight semantic parts in the text chat.
(Date, username and others)
2014-05-31 00:31:27 +02:00
'''
self.chat.tag_configure('date', foreground='#005d8f')
self.chat.tag_configure('name', foreground='#648f00')
self.chat.tag_configure('other', foreground='#de7a31')
self.chat.highlight(r'\[\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')
2013-02-15 19:28:32 +01:00
else:
2014-05-31 00:31:27 +02:00
self.chat.highlight(name, 'other')
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def show_menu(self, event):
2014-05-31 00:31:27 +02:00
'''Manages the opening and positioning of context menu'''
2014-01-17 15:27:08 +01:00
self.sidebar.index = self.sidebar.nearest(event.y)
if self.sidebar.index < 0:
2013-02-15 19:28:32 +01:00
self.menu2.post(event.x_root, event.y_root)
return
2014-01-17 15:27:08 +01:00
self.sidebar.activate(self.sidebar.index)
_, offset, _, height = self.sidebar.bbox(self.sidebar.index)
if event.y > height + offset + self.sidebar.size():
2013-02-15 19:28:32 +01:00
self.menu2.post(event.x_root, event.y_root)
else:
self.menu1.post(event.x_root, event.y_root)
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def recent_files(self):
2014-05-31 00:31:27 +02:00
'''Inserts recently opened files into the sidebar'''
for x, file in enumerate(self.prefs['chat']):
file = ' - ' + re.search('([^/]*)$', file).group(0)
2013-02-15 19:28:32 +01:00
if file not in self.sidebar.get(x):
self.sidebar.insert(x, file)
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def delete_file(self):
2014-05-31 00:31:27 +02:00
'''Removes the currently selected file from the sidebar'''
if self.prefs['Chat'][self.sidebar.index] == self.file:
self.file = ''
del self.prefs['Chat'][self.sidebar.index]
json.dump(self.prefs, open('prefs.json', 'w'))
2014-01-17 15:27:08 +01:00
self.read()
self.set_title()
def switch_file(self, _=None):
2014-05-31 00:31:27 +02:00
'''Opens the currently selected file from the sidebar in the chat'''
file = self.prefs['chat'][int(self.sidebar.curselection()[0])]
2013-02-15 19:28:32 +01:00
if self.file != file:
2013-08-25 19:09:55 +02:00
self.file = file
2014-01-17 15:27:08 +01:00
self.read()
self.set_title()
2014-01-17 15:27:08 +01:00
def open_file(self):
2014-05-31 00:31:27 +02:00
'''Selects a new file and opens it in the chat.'''
if sys.platform == 'darwin':
file = tkinter.filedialog.askopenfilename(
2014-05-31 00:31:27 +02:00
title='Choose a file...',
message='Open a conversation.',
2014-01-17 15:27:08 +01:00
**self.opzionifile)
else:
file = tkinter.filedialog.askopenfilename(
2014-05-31 00:31:27 +02:00
title='Choose a file...',
2014-01-17 15:27:08 +01:00
**self.opzionifile)
2014-05-31 00:31:27 +02:00
if file not in self.prefs['Chat'] and file != '':
self.prefs['chat'] += file,
json.dump(self.prefs, open('prefs.json', 'w'))
2013-08-25 19:09:55 +02:00
self.file = file
2014-01-17 15:27:08 +01:00
self.read()
self.set_title()
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def new_file(self):
2014-05-31 00:31:27 +02:00
'''Creates a new file and opens it in the chat'''
if sys.platform == 'darwin':
file = tkinter.filedialog.asksaveasfilename(
2014-05-31 00:31:27 +02:00
title='Create a file...',
message='Select the name of the new conversation.',
2014-01-17 15:27:08 +01:00
**self.opzionifile)
else:
file = tkinter.filedialog.asksaveasfilename(
2014-05-31 00:31:27 +02:00
title='Creates a new file...',
2014-01-17 15:27:08 +01:00
**self.opzionifile)
2014-05-31 00:31:27 +02:00
if file != '':
2013-02-15 19:28:32 +01:00
self.file = file
2014-05-31 00:31:27 +02:00
open(self.file, 'w').close()
if self.file not in self.prefs['chat']:
self.prefs['chat'] += self.file,
json.dump(self.prefs, open('prefs.json', 'w'))
2014-01-17 15:27:08 +01:00
self.read()
self.set_title()
def delete_not_found(self):
2014-05-31 00:31:27 +02:00
'''Removes files that are no longer available'''
for x, file in enumerate(self.prefs['chat']):
2013-02-15 19:28:32 +01:00
try:
open(file)
self.file = file
except FileNotFoundError:
2014-05-31 00:31:27 +02:00
del self.prefs['Chat'][x]
2013-02-15 19:28:32 +01:00
continue
2014-05-31 00:31:27 +02:00
json.dump(self.prefs, open('prefs.json', 'w'))
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def build_prefs(self):
2014-05-31 00:31:27 +02:00
'''Rebuild default preferences'''
2013-02-15 19:28:32 +01:00
default = {
2014-05-31 00:31:27 +02:00
'username': 'user',
'chat': ['chat.txt'],
'geometry': '800x500+500+250'
2013-02-15 19:28:32 +01:00
}
2014-05-31 00:31:27 +02:00
json.dump(default, open('prefs.json', 'w'))
self.prefs = json.load(open('prefs.json'))
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def edit_prefs(self, key, pref):
2014-05-31 00:31:27 +02:00
'''Changing a preference and saves it into an external file.'''
if pref != '':
2014-01-17 15:27:08 +01:00
self.prefs[key] = pref
2014-05-31 00:31:27 +02:00
json.dump(self.prefs, open('prefs.json', 'w'))
2013-08-25 19:09:55 +02:00
2014-01-17 15:27:08 +01:00
def close(self):
2014-05-31 00:31:27 +02:00
'''
Save the geometry of the window for the next boot,
closes and deletes the cache,
2014-01-17 15:27:08 +01:00
closes the window and exits.
2014-05-31 00:31:27 +02:00
'''
self.edit_prefs('geometry', window.geometry())
2013-02-15 19:28:32 +01:00
self.temp.close()
2014-01-17 15:27:08 +01:00
window.destroy()
2013-08-25 19:09:55 +02:00
2013-02-15 19:28:32 +01:00
def loop(self):
2014-05-31 00:31:27 +02:00
'''
2014-01-17 15:27:08 +01:00
Main loop
Update recent files and current covnersation.
2014-05-31 00:31:27 +02:00
'''
2014-01-17 15:27:08 +01:00
self.recent_files()
self.read()
2013-02-15 19:28:32 +01:00
self.after(200, self.loop)
2014-01-17 15:27:08 +01:00
window = tkinter.Tk()
app = Application()
app.mainloop()