Two things I consistently need when using a Text widget are scrollbars and pop-up menus. I've been adding them in on the fly for a while, but I think this class provides an easier way to implement these features.
I also added a couple of very minimal themes:
1. The terminal theme replaces the standard Text cursor with a blocky-style
insert cursor
2. The typewriter theme takes any text that has been inserted before it is called
and types it to the widget, one character at a time
There's an example of use at the bottom of the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | '''The SuperText class inherits from Tkinter's Text class and provides added
functionality to a standard text widget:
- Option to turn scrollbar on/off
- Right-click pop-up menu
- Two themes included (terminal and typewriter)
Compliant with Python 2.5-2.7
Author: @ifthisthenbreak
'''
from Tkinter import Tk, Frame, Text, Scrollbar, Menu
from tkMessageBox import askokcancel
class SuperText(Text):
def __init__(self, parent, scrollbar=True, **kw):
self.parent = parent
frame = Frame(parent)
frame.pack(fill='both', expand=True)
# text widget
Text.__init__(self, frame, **kw)
self.pack(side='left', fill='both', expand=True)
# scrollbar
if scrollbar:
scrb = Scrollbar(frame, orient='vertical', command=self.yview)
self.config(yscrollcommand=scrb.set)
scrb.pack(side='right', fill='y')
# pop-up menu
self.popup = Menu(self, tearoff=0)
self.popup.add_command(label='Cut', command=self._cut)
self.popup.add_command(label='Copy', command=self._copy)
self.popup.add_command(label='Paste', command=self._paste)
self.popup.add_separator()
self.popup.add_command(label='Select All', command=self._select_all)
self.popup.add_command(label='Clear All', command=self._clear_all)
self.bind('<Button-3>', self._show_popup)
def apply_theme(self, theme='standard'):
'''theme=['standard', 'typewriter', 'terminal']'''
if theme == 'typewriter':
'''takes all inserted text and inserts it one char every 100ms'''
text = self.get('1.0', 'end')
self.delete('1.0', 'end')
self.char_index = 0
self._typewriter([char for char in text])
elif theme == 'terminal':
'''blocky insert cursor'''
self.cursor = '1.0'
self.fg = self.cget('fg')
self.bg = self.cget('bg')
self.switch = self.fg
self.config(insertwidth=0)
self._blink_cursor()
self._place_cursor()
def _show_popup(self, event):
'''right-click popup menu'''
if self.parent.focus_get() != self:
self.focus_set()
try:
self.popup.tk_popup(event.x_root, event.y_root, 0)
finally:
self.popup.grab_release()
def _cut(self):
try:
selection = self.get(*self.tag_ranges('sel'))
self.clipboard_clear()
self.clipboard_append(selection)
self.delete(*self.tag_ranges('sel'))
except TypeError:
pass
def _copy(self):
try:
selection = self.get(*self.tag_ranges('sel'))
self.clipboard_clear()
self.clipboard_append(selection)
except TypeError:
pass
def _paste(self):
self.insert('insert', self.selection_get(selection='CLIPBOARD'))
def _select_all(self):
'''selects all text'''
self.tag_add('sel', '1.0', 'end-1c')
def _clear_all(self):
'''erases all text'''
isok = askokcancel('Clear All', 'Erase all text?', parent=self,
default='ok')
if isok:
self.delete('1.0', 'end')
def _typewriter(self, text): # theme: typewriter
'''after the theme is applied, this method takes all the inserted text
and types it out one character every 100ms'''
self.insert('insert', text[self.char_index])
self.char_index += 1
if self.char_index == len(text):
self.after_cancel(self.typer)
else:
self.typer = self.after(100, self._typewriter, text)
def _place_cursor(self): # theme: terminal
'''check the position of the cursor against the last known position
every 15ms and update the cursorblock tag as needed'''
current_index = self.index('insert')
if self.cursor != current_index:
self.cursor = current_index
self.tag_delete('cursorblock')
start = self.index('insert')
end = self.index('insert+1c')
if start[0] != end[0]:
self.insert(start, ' ')
end = self.index('insert')
self.tag_add('cursorblock', start, end)
self.mark_set('insert', self.cursor)
self.after(15, self._place_cursor)
def _blink_cursor(self): # theme: terminal
'''alternate the background color of the cursorblock tagged text
every 600 milliseconds'''
if self.switch == self.fg:
self.switch = self.bg
else:
self.switch = self.fg
self.tag_config('cursorblock', background=self.switch)
self.after(600, self._blink_cursor)
lorem = '''Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'''
def typewriter_sample():
typewriter = SuperText(root, scrollbar=False, font=('Times', 10, 'bold'))
typewriter.pack(fill='both', expand=True)
typewriter.insert(1.0, lorem)
typewriter.apply_theme(theme='typewriter')
def terminal_sample():
options = {'bg': 'black', 'fg': 'green', 'font': ('Courier', 10)}
terminal = SuperText(root, scrollbar=True)
terminal.pack(fill='both', expand=True)
terminal.insert(1.0, lorem)
terminal.config(options)
terminal.apply_theme(theme='terminal')
if __name__ == '__main__':
root = Tk()
typewriter_sample()
terminal_sample()
root.mainloop()
|