Continue from the
last post, we are going to implement the
Find button and wrap up the project.
We are going to use regular expression, so let's add an import statement:
import re
Next we implement
findnext() as below:
def findnext(self, pattern):
'''search pattern from cursor position onward.'''
if not pattern: # ask if no pattern specified
dlg = wx.TextEntryDialog(self, "Find?", "Attention", "",
style=wx.OK|wx.CANCEL)
if dlg.ShowModal() == wx.ID_OK:
pattern = dlg.GetValue()
if pattern:
self.lastfind = pattern
curpos = self.textbox.GetSelection()[1] # end of current selection
endpos = self.textbox.GetLastPosition() # eof
# retrieve the content in the text area from current position onward
content = self.textbox.GetRange(curpos, endpos)
# find the next match using regexp & case insensitive
mo = re.search(pattern, content, re.I)
# select the match and scroll to it
if mo:
self.textbox.SetSelection(curpos + mo.start(), curpos + mo.end())
self.textbox.ShowPosition(curpos + mo.start())
else:
dlg = wx.MessageDialog(None, 'Not found.', 'Attention', wx.OK)
dlg.ShowModal()
What it does is to ask the user for the search pattern if it is not provided, and then search the text area from the current position onward until it reaches the end. If a match is found, select it and scroll to it. It will also save the search pattern to an instance variable
lastfind for later use.
Next we write the Find button event handler, which simply calls
findnext() and then set the focus back to the text area:
def onFind(self, event):
self.findnext('')
self.textbox.SetFocus()
Next we implement
Find Next (F3). First we need to initialize the
lastfind instance variable to an empty string, as well as binding the
Key Press event in
__init__():
self.lastfind = ''
textbox.Bind(wx.EVT_KEY_DOWN, self.onKeyPress)
and implement the event handler as:
def onKeyPress(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_F3:
self.findnext(self.lastfind)
else:
event.Skip()
It simply calls
findnext() with the instance variable
lastfind. That means if this is the first time doing a find, the user will be asked to enter a search pattern because the instance variable
lastfind is empty.
That's it. Both
Find and
Find Next are now implemented!
To wrap up the project, I add a feature to change the default directory on startup if a particular environmental variable is set with the following code in
__init__():
# change to default directory
if os.environ.has_key('KASIO_PATH'):
os.chdir(os.environ['KASIO_PATH'])
Below is the complete source code for the GUI part of this project, with additional keyboard shortcuts for
Open,
Save,
Find and
Quit. Also in order to make it runs seamlessly, focus is switched back to the text area whenever a button is clicked.
# kasio.py
import re
import os
import sys
import wx
import filecrypto
class MainFrame(wx.Frame):
def __init__(self, parent=None, id=(-1), title='kasio', size=(800,600)):
wx.Frame.__init__(self, parent, id, title, size=size)
self.panel = wx.Panel(self, wx.ID_ANY)
textbox = wx.TextCtrl(self.panel, -1, style=wx.TE_MULTILINE)
font = wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL)
textbox.SetFont(font)
btnOpen = wx.Button(self.panel, -1, 'Open')
btnSave = wx.Button(self.panel, -1, 'Save')
btnFind = wx.Button(self.panel, -1, 'Find')
btnExit = wx.Button(self.panel, -1, 'Exit')
self.Bind(wx.EVT_BUTTON, self.onOpen, btnOpen)
self.Bind(wx.EVT_BUTTON, self.onSave, btnSave)
self.Bind(wx.EVT_BUTTON, self.onFind, btnFind)
self.Bind(wx.EVT_BUTTON, self.onExit, btnExit)
topSizer = wx.BoxSizer(wx.VERTICAL)
btnSizer = wx.BoxSizer(wx.HORIZONTAL)
btnSizer.Add(btnOpen, 0, wx.ALL, 1)
btnSizer.Add(btnSave, 0, wx.ALL, 1)
btnSizer.Add(btnFind, 0, wx.ALL, 1)
btnSizer.Add(btnExit, 0, wx.ALL, 1)
topSizer.Add(textbox, 1, wx.ALL|wx.EXPAND, 1)
topSizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 3)
topSizer.Add(btnSizer, 0, wx.ALL|wx.ALIGN_RIGHT, 3)
self.panel.SetSizer(topSizer)
#topSizer.Fit(self) # minimum size to show all controls
self.Centre() # center the frame in desktop
btnOpen.SetDefault()
self.filename = ''
self.password = ''
self.textbox = textbox
self.lastfind = ''
textbox.Bind(wx.EVT_KEY_DOWN, self.onKeyPress)
textbox.SetFocus()
# change to default directory
if os.environ.has_key('KASIO_PATH'):
os.chdir(os.environ['KASIO_PATH'])
def onOpen(self, event):
try:
# ask for file
dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*",
wx.OPEN|wx.CHANGE_DIR)
if dlg.ShowModal() == wx.ID_OK:
self.filename = dlg.GetPath()
else:
return
# ask for password
dlg = wx.TextEntryDialog(self, "Password?", "Attention", "",
style=wx.OK|wx.CANCEL|wx.TE_PASSWORD)
if dlg.ShowModal() == wx.ID_OK:
self.password = dlg.GetValue()
else:
return
# create file cipher
fc = filecrypto.FileCrypto()
# read & decrypt file
plaintext = fc.load(self.filename, self.password)
# convert 8-bit string in utf-8 encoding to unicode string
plaintext = plaintext.decode('utf-8')
# populate text control
self.textbox.SetValue(plaintext)
except:
self.filename = ''
self.password = ''
self.textbox.SetValue('')
errmsg = str(sys.exc_info()[1])
print(errmsg)
dlg = wx.MessageDialog(None, 'Password Incorrect!', 'Attention', wx.OK | wx.ICON_EXCLAMATION)
dlg.ShowModal()
finally:
self.textbox.SetFocus()
def onSave(self, event):
try:
# if no filename, ask filename and password
if len(self.filename) == 0:
# ask for filename
dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*",
wx.SAVE|wx.OVERWRITE_PROMPT|wx.CHANGE_DIR)
if dlg.ShowModal() == wx.ID_OK:
self.filename = dlg.GetPath()
else:
return
# ask for password
dlg = wx.TextEntryDialog(self, "Password?", "Attention", "",
style=wx.OK|wx.CANCEL|wx.TE_PASSWORD)
if dlg.ShowModal() == wx.ID_OK:
self.password = dlg.GetValue()
else:
return
# create file cipher
fc = filecrypto.FileCrypto()
# get the text
plaintext = self.textbox.GetValue()
# convert unicode string to 8-bit string in utf-8 encoding
plaintext = plaintext.encode('utf-8')
# encrypt and write to file
fc.save(self.filename, self.password, plaintext)
except:
errmsg = str(sys.exc_info()[1])
dlg = wx.MessageDialog(None, errmsg, 'Attention', wx.OK | wx.ICON_EXCLAMATION)
dlg.ShowModal()
print(errmsg)
finally:
self.textbox.SetFocus()
def onExit(self, event):
self.Close()
def onFind(self, event):
self.findnext('')
self.textbox.SetFocus()
def onKeyPress(self, event):
keycode = event.GetKeyCode()
if keycode == wx.WXK_F3:
self.findnext(self.lastfind)
elif keycode == ord('O') and event.ControlDown(): # Control-O
self.onOpen(None)
elif keycode == ord('S') and event.ControlDown(): # Control-S
self.onSave(None)
elif keycode == ord('F') and event.ControlDown(): # Control-F
self.onFind(None)
elif keycode == ord('Q') and event.ControlDown(): # Control-Q
self.onExit(None)
else:
event.Skip()
def findnext(self, pattern):
'''search pattern from cursor position onward.'''
if not pattern: # ask if no pattern specified
dlg = wx.TextEntryDialog(self, "Find?", "Attention", "", style=wx.OK|wx.CANCEL)
if dlg.ShowModal() == wx.ID_OK:
pattern = dlg.GetValue()
if pattern:
self.lastfind = pattern
curpos = self.textbox.GetSelection()[1] # end of current selection
endpos = self.textbox.GetLastPosition() # eof
# retrieve the content in the text area from current position onward
content = self.textbox.GetRange(curpos, endpos)
# find the next match using regexp & case insensitive
mo = re.search(pattern, content, re.I)
# select the match and scroll to it
if mo:
self.textbox.SetSelection(curpos + mo.start(), curpos + mo.end())
self.textbox.ShowPosition(curpos + mo.start())
else:
dlg = wx.MessageDialog(None, 'Not found.', 'Attention', wx.OK)
dlg.ShowModal()
class MainApp(wx.App):
def OnInit(self):
frame = MainFrame()
frame.Show(True)
self.SetTopWindow(frame)
return True
def main():
app = MainApp()
app.MainLoop()
if __name__=='__main__':
main()