Skip to content

Instantly share code, notes, and snippets.

@rochacbruno
Forked from MaxMorais/maskedentry.py
Created April 15, 2013 15:07
Show Gist options
  • Save rochacbruno/5388788 to your computer and use it in GitHub Desktop.
Save rochacbruno/5388788 to your computer and use it in GitHub Desktop.
#-------------------------------------------------------------------------------
# Name: maskedit.py
# Purpose:
#
# Author: Maxwell Morais ([email protected])
#
# Created: 10/04/2013
# Copyright: (c) Maxwell Morais 2013
# Licence: MIT
#-------------------------------------------------------------------------------
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import sys, re
if sys.version[0]=='2':
import Tkinter as tk
import ttk
elif sys.version[3]=='3':
import tkinter as tk
from tkinter import ttk
else:
print "Python version not supported, try 2 or 3 versios"
definitions = {
'9': '[0-9]',
'a': '[a-zA-Z]',
'x': '[a-zA-z0-9]'
}
class ToolTip(tk.Toplevel):
def __init__(self, root, **kw):
self.fields = {
'tooltip': None,
'variable': None,
'showdelay': 1,
'hidedelay': 10,
'root': root,
}
self.__hide_id = None
self.__show_id = None
for k in kw.keys():
if k in self.fields:
self.fields[k]=kw.pop(k)
if not 'variable' in kw and not self.fields['variable']:
self.fields['variable']=tk.StringVar(root)
tk.Toplevel.__init__(self, root, **kw)
self.withdraw()
self.overrideredirect(True)
self.setup()
def setup(self):
self._tooltip()
self._bind()
self._grid()
def set_text(self, value):
self.fields['variable'].set(value)
def get_text(self):
return self.fields['variable'].get()
def _tooltip(self):
if not isinstance(self.fields.get('variable', None), tk.StringVar):
self.fields['variable'] = tk.StringVar(self)
tooltiptext = self.fields['tooltip'] or self.fields['variable'].get()
self.fields['variable'].set(tooltiptext)
self._widget = ttk.Label(self,
textvariable=self.fields['variable'],
#aspect=1000,
background='#ffffcc'
)
def _grid(self):
self._widget.grid(row=0, column=0)
def _bind(self):
self.fields['root'].bind('<Enter>', self._init_tooltip, True)
self.fields['root'].bind('<Leave>', self._hide_tooltip, True)
def _init_tooltip(self, event):
self.__show_id = self.after(
int(self.fields['showdelay']*1000),
self._show_tooltip
)
def _show_tooltip(self, event=None):
self.__show_id = None
x = self.fields['root'].winfo_rootx()
y = self.fields['root'].winfo_rooty()
height = self.fields['root'].winfo_height()
width = self.fields['root'].winfo_width()
lx = x + self.winfo_width() + 5
if lx < self.fields['root'].winfo_screenwidth() - 5:
x += 5
else:
x += self.fields['root'].winfo_screenwidth() - lx
ly = (y + height + self.winfo_height() + 5)
if ly < self.fields['root'].winfo_height() - 5:
y += height + 5
else:
y += -(self.winfo_height()+5)
self.wm_geometry('+%d+%d'%(x,y))
self.deiconify()
self.__hide_id = self.after(
int(self.fields['hidedelay'])*1000,
self._hide_tooltip
)
def _hide_tooltip(self, event):
if self.__show_id is not None:
self.after_cancel( self.__show_id )
self.__show_id = None
if self.__hide_id is not None:
self.after_cancel( self.__hide_id )
self.__hide_id = None
self.withdraw()
class FormWidget(ttk.Frame):
_fields= {
'field': None,
'label': None,
'help': None,
'variable': None,
'labelvariable': None,
'helpvariable': None
}
_factory = {
'variable': tk.StringVar,
'widget': tk.Entry
}
def __init__(self, root, **kw):
self.fields = self._fields.copy()
for k in kw.keys():
if k in self.fields:
self.fields[k]=kw.pop(k)
if not 'variable' in kw:
self.fields['variable']=self._factory.get(
'variable',
tk.StringVar)(root)
ttk.Frame.__init__(self, root, **kw)
self.setup()
def setup(self):
self._label()
self._widget()
self._help()
self._grid()
def set(self, value): self.fields['variable'].set(value)
def get(self): return self.fields['variable'].get()
def set_label(self, value): self.fields['labelvariable'].set(value)
def get_label(self): return self.fields['labelvariable'].get()
def set_help(self, value): self.fields['helpvariable'].set(value)
def get_help(self): return self.fields['helpvariable'].get()
def _label(self):
labeltext = self.fields['label'] or self.fields['field']['label'] or ''
if not isinstance(self.fields.get('labelvariable', None), tk.StringVar):
self.fields['labelvariable'] = tk.StringVar(self)
self.fields['labelvariable'].set(labeltext)
self._wgt_label = ttk.Label(
self,
textvariable=self.fields['labelvariable']
)
def _widget(self):
self._widget = self._factory.get('widget', ttk.Entry)(
self,
textvariable=self.fields['variable']
)
def _help(self):
helptext = self.fields['help'] or ''
if not helptext: return
if not isinstance(self.fields.get('helpvariable', None), tk.StringVar):
self.fields['helpvariable'] = tk.StringVar(self)
self.fields['helpvariable'].set(helptext)
self._wgt_help = ToolTip(
self._widget,
variable=self.fields['helpvariable']
)
def _grid(self):
self._wgt_label.grid(row=0, column=0)
self._widget.grid(row=1, column=0)
class StringWidget(ttk.Entry):
@property
def variable(self):
return self.fields['textvariable']
class MaskedWidget(ttk.Entry):
@staticmethod
def make_expre(mask):
re_not_in_def = '([^%s]+)'%'|'.join(definitions.keys())
re_is_in_def = '([^%s]+)'%'|'.join(definitions.keys())
groups = re.split(re_not_in_def, mask.lower())[1:]
expre = ""
for group in groups:
keyed = False
for key in definitions.keys():
if not key in group: continue
atom = definitions[key]
expre += "(%s)"%(
atom if len(group)==1 else "%s{%d}"%(atom, len(group))
)
keyed = True
if not keyed:
expre += re.escape(group)
return expre
def __init__(self, master, type, **kw):
self.fields = {
'type': type,
'mask': None,
'monetary': False,
'dec_places': 2,
'dec_sep': '.',
'tho_places': 3,
'tho_sep': ',',
'symbol': '',
'fmt_neg': '-%(amount)s',
'fmt_pos': '%(amount)s',
'placeholder': '_',
'textvariable': None,
}
if str(type).lower() == 'fixed':
assert 'mask' in kw, 'the fixed mask, is not present'
self.fields['mask'] = kw.pop('mask', '').lower()
for k in kw.keys():
if k in self.fields:
if k!='textvariable':
self.fields[k]=kw.pop(k)
else:
self.fields[k]=kw[k]
if not 'textvariable' in kw:
self.fields['textvariable']=tk.StringVar(master)
kw['textvariable'] = self.fields['textvariable']
ttk.Entry.__init__(self, master, **kw)
self.defs = definitions
self.tests = []
self.partialPosition = None
self.firstNonMaskPosition = None
self.len = len(self.fields['mask'])
for i,c in enumerate(self.fields['mask'].lower()):
if c == '?':
self.len -= 1
self.partialPosition = i
atom = self.defs.get(c, None)
self.tests.append(re.compile('(%s)'%atom) if atom else atom)
if not atom and self.firstNonMaskPosition==None:
self.firstNonMaskPosition = len(self.tests)-1
self.writeBuffer()
if str(self.cget("state")).upper()!="DISABLED":
self.bind('<KeyPress>', self._onKeyPress, True)
self.bind('<KeyRelease>', lambda e: 'break', True)
self.bind('<FocusIn>', self._onFocusIn, True)
def clean_numeric(self, string):
if not isinstance(string, basestring): string = str(string)
string = string.replace(self.fields['tho_sep'], '')\
.replace(self.fields['dec_sep'], '.')
if not '.' in string:
string = list(string)
string.insert(-2, '.')
string = ''.join(string)
return string.partition(self.fields['dec_sep'])
def fmt_numeric( self, amount):
temp = '00' if not '.' in str(amount) \
else str(amount).split('.')[1]
l = []
amount = amount.split('.')[0]
minus = long(amount)<0
if len(amount)> self.fields['tho_places']:
nn = amount[len(amount)-self.fields['tho_places']:]
l.append(nn)
amount = amount[0:len(amount)-self.fields['tho_places']]
while len(amount) > self.fields['tho_places']:
nn = amount[len(amount)-self.fields['tho_places']:]
l.insert(0, nn)
amount = amount[0:len(amount)-self.fields['tho_places']]
if len(amount)>0: l.insert(0, amount)
amount = self.fields['tho_sep'].join(l)+self.fields['dec_sep']+temp
if minus:
amount = self.fields['fmt_neg']%{
'symbol':self.fields['symbol'],
'amount': amount
}
else:
amount = self.fields['fmt_pos']%{
'symbol': self.fields['symbol'],
'amount': amount
}
return amount
def seekNext(self, pos):
if 0 <= pos+1<self.len:
if self.tests[pos+1]:
return pos+1
return self.seekNext(pos+1)
return pos
def seekPrev(self, pos):
if 0 < pos -1 <=self.len:
if self.tests[pos-1]:
return pos-1
return self.seekPrev(pos-1)
return pos
def shiftL(self, begin, end):
if begin < 0: return
for i in range(self.len):
j = self.seekNext(begin)
if self.tests[i]:
if j < self.len and self.tests[i].match(self.buffer[i]):
self.buffer[i] = self.buffer[j]
self.buffer[j] = self.fields['placeholder']
else:
break
def shiftR(self, pos, c):
if pos in range(len(self.len)):
j = self.seekNext(pos)
t = self.buffer[pos]
if not t == c and j < self.len and t == self.fields['placeholder']:
self.buffer[pos] = c
def writeBuffer(self):
self.fields['textvariable'].set(
''.join(
filter(
lambda x: x!=None,
map(
lambda c, self=self:
(self.fields['placeholder']
if self.defs.get(c, None)
else c)
if c!='?' else None, self.fields['mask'])
)
)
)
return self.get()
def _onFocusIn(self, event):
if self.len>0 and self.tests[0]:
self.icursor(0)
else:
self.icursor(self.seekNext(0))
def _onKeyPress(self, event):
print event.keysym
if event.keysym == 'Tab':
return
elif event.keysym == 'Escape':
if self.fields['type'] == 'fixed':
self.writeBuffer()
else:
self.delete(0, len(event.widget.get()))
widget = event.widget
val = widget.get()
idx = widget.index(tk.INSERT)
if event.keysym == 'Left':
if 0 <= idx < self.len:
if idx < self.firstNonMaskPosition:
return 'break'
elif not self.tests[idx]:
widget.icursor(self.seekPrev(idx))
elif event.keysym == 'Right':
if 0 <= idx < self.len:
if idx >= self.len:
return 'break'
elif not self.tests[idx]:
widget.icursor(self.seekNext(idx))
elif event.keysym == 'BackSpace':
widget.event_generate('<Left>')
return 'break'
else:
if self.fields['type'] == 'fixed':
if 0<=idx<self.len and self.tests[idx]:
if not self.tests[idx].match(event.char):
widget.bell()
return 'break'
widget.delete(idx)
widget.insert(idx, event.char)
if idx < self.len:
_next = self.seekNext(idx)
if idx+1<_next:
widget.icursor(_next)
# widget.selection_from(idx+1, idx+2)
return 'break'
else:
self.bell()
return 'break'
elif self.fields['type'] == 'numeric' and event.char.isdigit():
if val:
widget.delete(0, len(val))
head, sep, tail = self.clean_numeric(val)
else:
head, sep, tail = '0', '.', '0'
if len(tail or '')<2:
tail = (tail or '') + event.char
else:
#if (int(tail)+int(event.char))>99:
head += tail[0]
tail = tail[1:] + event.char
#else:
# tail = str(int(tail)+int(event.char))
#tail = tail[1:] + event.char
widget.insert(0,
self.fmt_numeric(
''.join([head, sep, tail])
)
)
return 'break'
else:
self.bell()
return 'break'
def main():
root = tk.Tk()
page = tk.Frame(root)
MaskedWidget(page, 'fixed', mask='+99 (99) 999-999-999', width=20).pack()
MaskedWidget(page, 'fixed', mask='99/99/9999').pack()
MaskedWidget(page, 'numeric').pack()
FormWidget(page,
label=u'Olá',
help='Seu Nome'
).pack()
page.pack()
root.mainloop()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment