2011-11-06

Kasio - Find Button

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()



No comments: