File: kiki.py

package info (click to toggle)
kiki 0.5.6-4
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 316 kB
  • ctags: 121
  • sloc: python: 486; makefile: 7
file content (717 lines) | stat: -rw-r--r-- 34,281 bytes parent folder | download | duplicates (3)
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
"""Kiki 0.5.6 - A Free Environment for Regular Expression Testing (ferret)

Copyright (C) 2003, 2004 Project 5

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Contact info
web: http://come.to/project5
mail/msn: project5@wanadoo.nl
icq: 84243714
"""

import wxversion
wxversion.select('2.6')

import wx
import wx.html

__version__ = "0.5.6"

import re, os, os.path, cgi, sys

# string constants
WINDOWSIZE = "MainWindowSize"
WINDOWPOSITION = "MainWindowPosition"
SASHPOSITION = "SashPosition"
SEARCHTYPE = "SearchType"

MATCHINDEX = "MatchIndex"
MATCHTEMPLATE = "MatchTemplate"
FORMATTEDMATCH = "FormattedMatch"
STARTTEMPLATE = "StartTemplate"
ENDTEMPLATE = "EndTemplate"
GROUPCOLOR = "GroupColor"
GROUPNUMBER = "GroupNumber"

SAMPLETEXT = "SampleText"
REGEX = "Regex"

RESULTS_SHOWMATCHES = "ShowMatchesGroups"
RESULTS_SHOWNAMEDGROUPS = "ShowNamedGroups"
RESULTS_SHOWSAMPLE = "ShowSampleTextInResults"

# flags below are used in settings and in eval() function, so do NOT change!
FLAGIGNORE = "re.IGNORECASE"
FLAGMULTILINE = "re.MULTILINE"
FLAGLOCALE = "re.LOCALE"
FLAGDOTALL = "re.DOTALL"
FLAGUNICODE = "re.UNICODE"
FLAGVERBOSE = "re.VERBOSE"

# constants required by the GUI
ni = wx.NewId
ID_EVALUATE = ni()
ID_NOTEBOOK = ni()
ID_HELPCOMBOBOX = ni()

# global variable
settings = None

# templates
t_error = """<font color="maroon"><b>Error:</b></font><br /><ul>%s</ul>"""
t_nomatch = """<font color="teal"><b>No match found</b></font>"""

# colors used to highlight matches
colors = ["0000AA" , "00AA00" , "FFAA55" , "AA0000" , "00AAAA" , "AA00AA" , "AAAAAA" , 
          "0000FF" , "00FF00" , "00FFFF" , "FF0000" , "DDDD00" , "FF00FF" , 
          "AAAAFF" , "FF55AA" , "AAFF55" , "FFAAAA" , "55AAFF" , "FFAAFF" , 
          "000077" , "007700" , "770000" , "007777" , "770077" , "777700" ]

class Settings(object):
    """Stores and retrieves settings to a file as Python data
       structures which are eval()-ed. This is not by definition
       safe, but since the user has access to source code anyway
       and hence the ability to screw up anything, the danger 
       seems quite limited."""
    def __init__(self, savedir=None, dirname="", filename="settings.py", debugfile=""):
        """Initializes the object
           
           Arguments:
           savedir -- directory where to store data
                      For more info, see the docs of Settings.setSaveDir()
           dirname -- if savedir=None, dirname is used to determine which
                      subdirectory of $HOME data is stored it.
           filename -- name of file containing stored data
           debugfile -- used to allow the user to manually override the
                        default storage dir. Mor info in the docs of
                        Settings.setSaveDir()
        """
        self.__setSaveDir(savedir, dirname, debugfile)
        self.savefilename = filename
        self.__load()
        
    def shutdown(self):
        """Must be called before the program ends."""
        self.save()
        
    def __setSaveDir(self, savedir=None, dirname="", debugfile=""):
        """Sets self.savedir
           
           Arguments:
           savedir -- directory where to store data
                      if savedir==None, $HOME/dirname is used.
                      If necessary, the directory is created.
           dirname -- if savedir==None, dirname determines which
                      subdirectory of the home dir data is stored 
                      in
           debugfile -- this file may be a filename which is imported
                        and may contain a variable called savedir
                        If the debugfile is specified, imported
                        successfully and savedir exists, it is used
                        to override any other parameters. This allows
                        users to override the directory, bypassing
                        the application which needs the settings.
           
           In some Windows installations (seems to be mainly a Win2k
           problem), $HOME points to the root directory. In this case,
           this function will try to use $USERPROFILE/dirname instead.
           
           Overriding this default behaviour is possible by supplying the
           savedir variable in pearsdebug.py."""
        if savedir==None:
            try: # try to override using debugfile if present and contains savedir
                debugmodule = __import__(debugfile, globals())
                savedir = debugmodule.savedir
            except: # if no override, then perform default actions
                if savedir == None: # use $HOME/dirname
                    savedir = os.path.expanduser('~/'+dirname)
                    if len(savedir)<=len("c:\\/"+dirname): # sometimes $HOME points to root
                        # if this is the case, try using $USERPROFILE (see docstring)
                        temp = os.path.join(os.path.expandvars('$USERPROFILE'), dirname)
                        # if this is a different location, use it!
                        if temp > len("C:\\/"+dirname):
                            savedir = temp
            # create dir if it doesn't exist
        if not os.path.exists(savedir):
            os.makedirs(savedir)
        self.savedir = savedir
        
    def __load(self):
        """Loads the settings from a file. These settings are saved
           in Python code format which is eval()-ed, so it's not
           trustworthy."""
        try:
            settingsfile = file(os.path.join(self.savedir, self.savefilename), "r")
            self.settings = eval(settingsfile.read())
            settingsfile.close()
        except: # if file doesn't exist
            self.settings= {}
        
    def save(self):
        """Saves the settings to a file."""
        settingsfile = file(os.path.join(self.savedir, self.savefilename), "w")
        settingsfile.write(str(self.settings))
        settingsfile.close()
        
    def set(self, settingname, value):
        """Changes the value of a setting with settingname to value."""
        self.settings[settingname] = value
        
    def get(self, settingname, defaultval=None):
        """Returns the setting with settingname if present, otherwise
           returns defaultval and saves settingname with defaultval."""
        if self.settings.has_key(settingname):
            return self.settings[settingname]
        else:
            self.set(settingname, defaultval)
            return defaultval


class MyHtmlWindow(wx.html.HtmlWindow):
    """Adds OnLinkClicked"""
    def __init__(self, parent, id):
        wx.html.HtmlWindow.__init__(self, parent, id, style=wx.NO_FULL_REPAINT_ON_RESIZE)
    def OnLinkClicked(self, linkinfo):
        # there's a problem in KDE: the browser is not recognized properly
        # circumvent this problem
        import os
        if os.environ.has_key("BROWSER") and \
           os.environ["BROWSER"]=='kfmclient openProfile webbrowsing':
            print "Invalid browser detected : %s\nResetting to konqueror." % os.environ["BROWSER"]
            os.environ["BROWSER"] = 'konqueror' # set it to konqueror
        import webbrowser # MUST be imported only AFTER os.environ has been modified
        webbrowser.open(linkinfo.GetHref(), 1)
        
        
class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SplitterWindow = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_LIVE_UPDATE)
        self.BottomPane = wx.Panel(self.SplitterWindow, -1)
        self.Notebook = wx.Notebook(self.BottomPane, ID_NOTEBOOK, style=0)
        self.HelpPane = wx.Panel(self.Notebook, -1)
        self.SampleTextPane = wx.Panel(self.Notebook, -1)
        self.MatchesPane = wx.Panel(self.Notebook, -1, style=wx.SUNKEN_BORDER|wx.TAB_TRAVERSAL)
        self.TopPane = wx.Panel(self.SplitterWindow, -1, style=wx.RAISED_BORDER|wx.TAB_TRAVERSAL)
        self.RegexBox = wx.ComboBox(self.TopPane, -1, choices=["", "", "", "", "", "", ""], style=wx.CB_DROPDOWN)
        self.EvaluateButton = wx.Button(self.TopPane, ID_EVALUATE, "Evaluate")
        self.MethodBox = wx.RadioBox(self.TopPane, -1, "Methods", choices=["Find all", "Find first"], majorDimension=1, style=wx.RA_SPECIFY_COLS)
        self.IgnoreCheckBox = wx.CheckBox(self.TopPane, -1, "IGNORECASE (I)")
        self.LocaleCheckBox = wx.CheckBox(self.TopPane, -1, "LOCALE (L)")
        self.MultilineCheckBox = wx.CheckBox(self.TopPane, -1, "MULTILINE (M)")
        self.DotAllCheckBox = wx.CheckBox(self.TopPane, -1, "DOTALL (S)")
        self.UnicodeCheckBox = wx.CheckBox(self.TopPane, -1, "UNICODE (U)")
        self.VerboseCheckBox = wx.CheckBox(self.TopPane, -1, "VERBOSE (X)")
        self.MatchesWindow = wx.html.HtmlWindow(self.MatchesPane, -1)
        self.SampleText = wx.TextCtrl(self.SampleTextPane, -1, "", style=wx.TE_PROCESS_TAB|wx.TE_MULTILINE|wx.TE_RICH)
        self.HelpSelection = wx.ComboBox(self.HelpPane, ID_HELPCOMBOBOX, choices=["Working with Kiki", "Re - syntax", "Re - special characters: |, (, +, etc.", "Re - extensions, groups and lookahead/lookbehind: (?...)", "Re - special sequences: \...", "Re - flags", "About Kiki"], style=wx.CB_DROPDOWN|wx.CB_READONLY)
        self.HelpWindow = MyHtmlWindow(self.HelpPane, -1)

        self.__set_properties()
        self.__do_layout()
        # end wxGlade

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle("Kiki")
        self.SetSize((640, 480))
        self.RegexBox.SetSelection(0)
        self.EvaluateButton.SetDefault()
        self.MethodBox.SetToolTipString("Find all returns all matches, find first only the first one")
        self.MethodBox.SetSelection(0)
        self.IgnoreCheckBox.SetToolTipString("Perform case-insensitive matching\nExpressions like [A-Z] will match lowercase letters too. This is not affected by the current locale.")
        self.LocaleCheckBox.SetToolTipString("Make \w, \W, \\b, and \B dependent on the current locale.")
        self.MultilineCheckBox.SetToolTipString("When specified, the pattern characters \"^\" and \"$\" match at the beginning respectively end of the string and at the beginning respectively end of each line (immediately following respectively preceding each newline).\nOtherwise \"^\" matches only at the beginning of the string, and \"$\" only at the end of the string and immediately before the newline (if any) at the end of the string.")
        self.DotAllCheckBox.SetToolTipString("Make the \".\" special character match any character at all, including a newline. Without this flag, \".\" will match anything except a newline.")
        self.UnicodeCheckBox.SetToolTipString("Make \w, \W, \\b, and \B dependent on the Unicode character properties database.")
        self.VerboseCheckBox.SetToolTipString("This flag allows you to write regular expressions that look nicer.\nWhitespace within the pattern is ignored, except when in a character class or preceded by an unescaped backslash, and, when a line contains a \"#\" neither in a character class or preceded by an unescaped backslash, all characters from the leftmost such \"#\" through the end of the line are ignored. ")
        self.HelpSelection.SetSelection(0)
        self.BottomPane.SetSize((623, 324))
        self.SplitterWindow.SplitHorizontally(self.TopPane, self.BottomPane, 120)
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        MainSizer = wx.BoxSizer(wx.HORIZONTAL)
        TopPaneSizer = wx.BoxSizer(wx.HORIZONTAL)
        HelpPaneSizer = wx.FlexGridSizer(3, 1, 7, 7)
        SampleTextSizer = wx.BoxSizer(wx.HORIZONTAL)
        MatchesPaneSizer = wx.BoxSizer(wx.HORIZONTAL)
        BottomPaneSizer = wx.FlexGridSizer(5, 3, 0, 0)
        OptionsSizer = wx.FlexGridSizer(1, 2, 0, 7)
        FlagsSizer = wx.StaticBoxSizer(wx.StaticBox(self.TopPane, -1, "Flags"), wx.HORIZONTAL)
        FlagCheckSizer = wx.FlexGridSizer(4, 3, 7, 15)
        RegexSizer = wx.FlexGridSizer(1, 2, 0, 7)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        RegexSizer.Add(self.RegexBox, 0, wx.EXPAND, 0)
        RegexSizer.Add(self.EvaluateButton, 0, 0, 0)
        RegexSizer.AddGrowableCol(0)
        BottomPaneSizer.Add(RegexSizer, 1, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        OptionsSizer.Add(self.MethodBox, 0, wx.EXPAND, 0)
        FlagCheckSizer.Add((7, 2), 0, wx.EXPAND, 0)
        FlagCheckSizer.Add((7, 2), 0, wx.EXPAND, 0)
        FlagCheckSizer.Add((7, 2), 0, wx.EXPAND, 0)
        FlagCheckSizer.Add(self.IgnoreCheckBox, 0, 0, 0)
        FlagCheckSizer.Add(self.LocaleCheckBox, 0, 0, 0)
        FlagCheckSizer.Add(self.MultilineCheckBox, 0, 0, 0)
        FlagCheckSizer.Add(self.DotAllCheckBox, 0, 0, 0)
        FlagCheckSizer.Add(self.UnicodeCheckBox, 0, 0, 0)
        FlagCheckSizer.Add(self.VerboseCheckBox, 0, 0, 0)
        FlagCheckSizer.Add((7, 2), 0, wx.EXPAND, 0)
        FlagCheckSizer.Add((7, 2), 0, wx.EXPAND, 0)
        FlagCheckSizer.Add((7, 2), 0, wx.EXPAND, 0)
        FlagsSizer.Add(FlagCheckSizer, 1, wx.EXPAND, 0)
        OptionsSizer.Add(FlagsSizer, 1, 0, 0)
        BottomPaneSizer.Add(OptionsSizer, 1, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        BottomPaneSizer.Add((7, 7), 0, wx.EXPAND, 0)
        self.TopPane.SetAutoLayout(1)
        self.TopPane.SetSizer(BottomPaneSizer)
        BottomPaneSizer.Fit(self.TopPane)
        BottomPaneSizer.SetSizeHints(self.TopPane)
        BottomPaneSizer.AddGrowableCol(1)
        MatchesPaneSizer.Add(self.MatchesWindow, 1, wx.EXPAND, 0)
        self.MatchesPane.SetAutoLayout(1)
        self.MatchesPane.SetSizer(MatchesPaneSizer)
        MatchesPaneSizer.Fit(self.MatchesPane)
        MatchesPaneSizer.SetSizeHints(self.MatchesPane)
        SampleTextSizer.Add(self.SampleText, 1, wx.EXPAND, 0)
        self.SampleTextPane.SetAutoLayout(1)
        self.SampleTextPane.SetSizer(SampleTextSizer)
        SampleTextSizer.Fit(self.SampleTextPane)
        SampleTextSizer.SetSizeHints(self.SampleTextPane)
        HelpPaneSizer.Add((1, 1), 0, wx.EXPAND, 0)
        HelpPaneSizer.Add(self.HelpSelection, 0, wx.EXPAND, 0)
        HelpPaneSizer.Add(self.HelpWindow, 1, wx.EXPAND, 0)
        self.HelpPane.SetAutoLayout(1)
        self.HelpPane.SetSizer(HelpPaneSizer)
        HelpPaneSizer.Fit(self.HelpPane)
        HelpPaneSizer.SetSizeHints(self.HelpPane)
        HelpPaneSizer.AddGrowableRow(2)
        HelpPaneSizer.AddGrowableCol(0)
        self.Notebook.AddPage(self.MatchesPane, "Matches")
        self.Notebook.AddPage(self.SampleTextPane, "Sample text")
        self.Notebook.AddPage(self.HelpPane, "Help")
        TopPaneSizer.Add(wx.NotebookSizer(self.Notebook), 1, wx.EXPAND, 0)
        self.BottomPane.SetAutoLayout(1)
        self.BottomPane.SetSizer(TopPaneSizer)
        MainSizer.Add(self.SplitterWindow, 1, wx.EXPAND, 0)
        self.SetAutoLayout(1)
        self.SetSizer(MainSizer)
        self.Layout()
        self.Centre()
        # end wxGlade

# end of class MyFrame

class MyFrameWithEvents(MyFrame):
    """Subclasses MyFrame - generated by wxGlade - and adds events."""
    def __init__(self, *args, **kwargs):
        MyFrame.__init__(self, *args, **kwargs)
            
        # map option flags to checkboxes
        self.flagmapper = {FLAGIGNORE: self.IgnoreCheckBox,
                           FLAGMULTILINE: self.MultilineCheckBox,
                           FLAGLOCALE: self.LocaleCheckBox,
                           FLAGDOTALL: self.DotAllCheckBox,
                           FLAGUNICODE: self.UnicodeCheckBox,
                           FLAGVERBOSE: self.VerboseCheckBox}
                               
        self.SetTitle(" " + self.GetTitle() + " " + __version__)
            
        # set empty pages for HTML windows
        self.MatchesWindow.SetPage("")
        self.HelpWindow.SetPage("")
        self.HelpWindow.SetWindowStyleFlag(wx.SUNKEN_BORDER)
            
        self.Notebook.SetSelection(0)
        self.RegexBox.Clear()
            
        # bind events
        wx.EVT_BUTTON(self, ID_EVALUATE, self.evaluate)
        wx.EVT_CLOSE(self, self.close)
        wx.EVT_NOTEBOOK_PAGE_CHANGED(self, ID_NOTEBOOK, self.changePage)
        wx.EVT_COMBOBOX(self, ID_HELPCOMBOBOX, self.showhelp)
            
        # apply settings
        self.loadSettings()
            
        # move focus to regex input field
        self.RegexBox.SetFocus()
            
        # initialize needed attribs
        self.matches = [] # list of found matches
            
        self.path = os.path.split(sys.argv[0])[0] or os.getcwd() # remembers where Kiki is located
	self.doc_path = "/usr/share/doc/kiki"
        
    def icon(self, path=None):
        """Load and assign the icon
            
           Arguments:
           path -- path where kiki.ico is located. If
                   path==None, the current directory is
                   used
        """
        import sys
        if path==None:
            self.path = os.path.split(sys.argv[0])[0] or os.getcwd() # *MUST* be the directory where everything, including About data and the likes are located
        else:
            self.path = path
        iconfile = os.path.join(self.path, "kiki.ico")
        iconfile = "/usr/share/pixmaps/kiki.ico"
        theicon = wx.Icon(iconfile, wx.BITMAP_TYPE_ICO)
        self.SetIcon(theicon)
        
    def changePage(self, event):
        """Handles notebook page changes"""
        if event.GetSelection()==2 and not self.HelpWindow.GetOpenedPageTitle().strip():
            self.HelpWindow.SetPage(file(os.path.join(self.doc_path, "index.html"),"r").read())
        
    def showhelp(self, event):
        """Handles help combo box events"""
        sel = self.HelpSelection.GetStringSelection().lower() # must lower-case for comparisons
        id = self.HelpSelection.GetSelection()
        filename, anchor = "", ""
        simpleload = True
        if id==0: # show main help
            filename = "index.html"
        elif sel.find("syntax")>-1:
            filename, anchor = "re.html", "syntax"
        elif sel.find("special characters")>-1:
            filename, anchor = "re.html", "specialcharacters"
        elif sel.find("extensions")>-1:
            filename, anchor = "re.html", "extensions"
        elif sel.find("special sequences")>-1:
            filename, anchor = "re.html", "specialsequences"
        elif sel.find("flags")>-1:
            filename, anchor = "re.html", "flags"
        else:
            simpleload = False
        if simpleload:
            filename = os.path.join(self.doc_path, filename)
            if anchor.strip():
                anchor = "#" + anchor
            else:
                anchor = ""
            self.HelpWindow.LoadPage(filename+anchor)
        else: # build about-screen
            f = file(os.path.join(self.doc_path, "about.html"), "r")
            about = f.read()
            f.close()
            # build the dictionary needed to format the string
            if self.GetTitle().lower().find("spe")>-1:
                spe = "active"
            else: spe = "inactive (Kiki running standalone)"
            d = {"version": __version__,
                 "kikidir": self.path,
                 "datadir": settings.savedir,
                 "pythonversion": sys.version.split()[0],
                 "wxpythonversion": wx.__version__,
                 "spe": spe,
                 "website": "http://come.to/project5",
                 "mail": "project5@wanadoo.nl",
                 "icq": "84243714" }
            about = about % d
            self.HelpWindow.SetPage(about)
        
    def evaluate(self, event):
        """Actual functionality, triggered by Evaluate button press.
           
           The regex is compiled if possible.
           If compilation is successful, the regex is added to the
           history and it's matched against the sample text.
           If compilation is unsuccessful, the error message is 
           displayed.
        """
        self.saveSettings()
        self.Notebook.SetSelection(0) # display output pane
        # STEP 1: try to compile the regex
        # get flags to use in the compilation
        flags = 0
        for flag in self.flagmapper.keys():
            if self.flagmapper[flag].IsChecked():
                flags = flags|eval(flag)
        # compile the regex and stop with error message if invalid
        try:
            self.MatchesWindow.SetPage("")
            regex = re.compile(self.RegexBox.GetValue(), flags)
        except re.error, e:
            self.MatchesWindow.SetPage(t_error % e)
            return False # stop execution if error
            
        if self.RegexBox.GetValue().strip(): # append to history if non-empty
            # get current history items
            currentitems = [self.RegexBox.GetValue()]
            for i in range(self.RegexBox.GetCount()):
                t = self.RegexBox.GetString(0)
                if not t in currentitems: # no duplicates
                    currentitems.append(t)
                self.RegexBox.Delete(0)
            for item in currentitems:
                self.RegexBox.Append(item)
            self.RegexBox.SetSelection(0) # set selection again
            
        rawtext = self.SampleText.GetValue()
        # insider joke for the Sluggy fans
        if rawtext.strip()=="INSTANT FERRET-SHOCK!":
            import random
            shock = ["<b>"]
            #blinkychars = """+*-/\:;][{}|tyqfghjkl?>ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%"""
            rc = random.choice
            c = colors
            sa = shock.append
            t = """<font color="#%s">KIKI</font>"""
            [ sa(t % (rc(c))) for i in xrange(10000) ]
            shock.append("</b>")
            self.MatchesWindow.SetPage("".join(shock))
            return None
            
        self.matches = [] # empty the list of match objects
        output = []
        while 1:
            if len(self.matches)==0:
                start = 0
            else:
                start = self.matches[-1].end()
                if len(self.matches[-1].group(0))==0: 
                    # in case of expressions which return empty matches
                    # Without this condition, an endless loop would occur.
                    start += 1
            match = regex.search(rawtext, start)
            if (not match) or \
               (len(self.matches)>=1 and self.MethodBox.GetSelection()==1) or \
               start>=len(rawtext): # this last condition is also to prevent an endless loop
                break # stop execution
            else:
                self.matches.append(match)
        if not self.matches: # if no matches found
            output.append(t_nomatch)
        else: # matches found
            # show matches with parentheses
            if settings.get(RESULTS_SHOWMATCHES, True):
                output.append(self.showmatches())
                output.append("""<br /><br />""")
            # show tabular overview of named groups
            if settings.get(RESULTS_SHOWNAMEDGROUPS, True):
                # TODO: self.shownamedgroups()
                pass
        if settings.get(RESULTS_SHOWSAMPLE, True): 
            output.append("""<table cellpadding="0" cellspacing="0"><tr><td>
                               <b>Sample text matched against:</b>
                             </td></tr>
                             <tr><td bgcolor="#EEEEEE"><i>""")
            output.append(self.htmlize(rawtext) or "&nbsp;") # something must be in there, so if no raw text, at least force a space
            output.append("""</i></td></tr></table>""")
        self.MatchesWindow.SetPage("".join(output))
            
    def htmlize(self, text):
        """Converts the text to html (escapes HTML entities and tags,
           converts spaces to &nbsp's and enters to <br>'s."""
        result = cgi.escape(text)
        result = result.replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;")
        result = result.replace("  ", "&nbsp;&nbsp;")
        result = result.replace("\r\n", "<br />")
        result = result.replace("\r", "<br />")
        result = result.replace("\n", "<br />")
        return result
        
    def formatmatch(self, match, index, matchtemplate, starttemplate, endtemplate):
        """Pretty-prints a match as HTML, with colors for groups, etc."""
        # first, make a dictionary of start and end positions 
        # (each char number in the match may be mapped to zero, one or more
        # group numbers which start/end there)
        # Groups function according to first-to-open, last-to-close.
        starts, ends = {}, {}
        # populate the dictionaries with empty lists 
        # A -1 key is necessary for groups which do not participate in the match.
        for pos in range(-1, len(match.string)+1):
            starts[pos], ends[pos] = [], []
        # now populate them with real values: each position key will contain
        # none or more group numbers
        #print match.groups()
        for groupnr in range(1+len(match.groups())):
            #print groupnr
            #print starts, ends
            starts[match.start(groupnr)].append(groupnr)
            ends[match.end(groupnr)].append(groupnr)
        # prepare result
        result = []
        # now, loop through the string matched and build the layout
        string = match.string
        opengroups = [] # keeps track of open groups
        for pos in range(match.start(), match.end()+1):
            while 1:
                # first, try to shut down any open groups
                if opengroups and opengroups[-1] in ends[pos]: # if opengroups available and the last one must be closed now
                    # shut down open group and remove it from the opengroups list
                    result.append(endtemplate % {GROUPCOLOR: colors[opengroups[-1] % len(colors)],
                                                 GROUPNUMBER: opengroups.pop(-1)})
                # secondly, try to open any new groups
                elif starts[pos]: # if any new groups must be opened now
                    result.append(starttemplate % {GROUPCOLOR: colors[starts[pos][0] % len(colors)],
                                                   GROUPNUMBER: starts[pos][0]})
                    opengroups.append(starts[pos].pop(0))
                else: # if no groups must be opened or closed, nothing special going on
                    if pos<match.end():
                        result.append(self.htmlize(string[pos]))
                    break # stop the while loop
        result = "".join(result)
        return matchtemplate % {MATCHINDEX: index, 
                                FORMATTEDMATCH: result}
        
    def showmatches(self):
        """Converts the results to html code and returns that.
           Is not capable of handling an empty self.matches list of matches."""
        if not self.matches:
            raise ValueError, "self.matches must be non-empty list of re match opbjects"
        index = -1 # number of current match that's being printed
        html = []
        html.append("""<table cellpadding="0" cellspacing="0"><tr><td bgcolor="#F8F8F8"><font color="#777777">""")
        # load match layout templates
        starttemplate = settings.get(STARTTEMPLATE, """<font color="#%%(%s)s"><b>(</b></font>""" % GROUPCOLOR)
        endtemplate = settings.get(ENDTEMPLATE, """<font color="#%%(%s)s"><b>)<font size="0">%%(%s)s</font></b></font>""" % (GROUPCOLOR, GROUPNUMBER))
        matchtemplate = settings.get(MATCHTEMPLATE, """<font color="#000000"><b><u><b><font size="0">%%(%s)s:</font></b></u>%%(%s)s</b></font>""" % (MATCHINDEX, FORMATTEDMATCH))
        # loop through matches and create output
        for match in self.matches:
            index += 1
            # determine what part of the string we're looking at
            if index>0:
                prevmatchend = self.matches[index-1].end()
            else: 
                prevmatchend = 0
            if index+1 < len(self.matches):
                nextmatchstart = self.matches[index+1].start()
            else:
                nextmatchstart = -1
            # append piece in between matches
            html.append(self.htmlize(match.string[prevmatchend:match.start()]))
            # append current match
            #html.append(self.htmlize(match.string[match.start():match.end()]))
            html.append(self.formatmatch(match, index, matchtemplate, starttemplate, endtemplate))
            # append end piece if necessary
            if index+1 >= len(self.matches): # if last match, print rest of string
                html.append(self.htmlize(match.string[match.end():]))
        html.append("""</font></td></tr></table>""")
        res = "".join(html)
        return res
        
    def loadSettings(self):
        """Loads GUI settings from the settings system."""
        # load some size settings
        # set window size and position; make sure it's on screen
        # and has a reasonable size
        system = wx.SystemSettings_GetMetric
        pos = list(settings.get(WINDOWPOSITION, (-1,-1)))
        if pos[0]<-1: pos[0] = 0
        if pos[1]<-1: pos[1] = 0
        if pos[0]>system(wx.SYS_SCREEN_X)-50:
            pos[0] = 0
        if pos[1]>system(wx.SYS_SCREEN_Y)-50:
            pos[1] = 0
        self.SetPosition(pos)
        size = list(settings.get(WINDOWSIZE, (-1, -1)))
        if size[0]>system(wx.SYS_SCREEN_X):
            size[0] = 640
        if size[1]>system(wx.SYS_SCREEN_Y):
            size[1] = 480
        size[0] = max(size[0], 640)
        size[1] = max(size[1], 480)
        self.SetSize(size)
            
        # load the sample text and regex last used
        self.SampleText.SetValue(settings.get(SAMPLETEXT, ""))
        self.RegexBox.SetValue(settings.get(REGEX, ""))
            
        # load the flags and desired type of re functionality
        for flag in self.flagmapper.keys():
            self.flagmapper[flag].SetValue(settings.get(flag, False))
        self.MethodBox.SetSelection(settings.get(SEARCHTYPE, 0))
            
        # other settings
        self.SplitterWindow.SetSashPosition(settings.get(SASHPOSITION, 100))
        
    def saveSettings(self, dosave=True):
        """Puts all GUI settings in the settings system. 
           
           Arguments:
           dosave -- if True, the save() method of Settings is called
                     when done
        """
        # put all stuff that needs saving in the settings
        settings.set(WINDOWSIZE, self.GetSize())
        settings.set(WINDOWPOSITION, self.GetPosition())
        settings.set(SASHPOSITION, self.SplitterWindow.GetSashPosition())
            
        settings.set(SAMPLETEXT, self.SampleText.GetValue())
        settings.set(REGEX, self.RegexBox.GetValue())
            
        # save the selected flags
        for flag in self.flagmapper.keys():
            settings.set(flag, self.flagmapper[flag].GetValue())
        settings.set(SEARCHTYPE, self.MethodBox.GetSelection())
            
        if dosave: settings.save()
        
    def close(self, event):
        """Prepares for shutdown and then closes the app."""
        self.saveSettings()
            
        # shut down the settings system
        settings.shutdown()
            
        # shut down the app
        self.Destroy()
        
def speCreate(parent, info=None):
    """Integration of Kiki into spe (http://spe.pycs.net)"""
    global settings
    settings = Settings(dirname=".spe", filename="kikicfg.py", debugfile="kikidebug")        
    Kiki = MyFrameWithEvents(parent, -1, "")
    Kiki.SetTitle(Kiki.GetTitle() + " - the ferret in your Spe")
    if info and info.has_key('kikiPath'):
        kikipath = info['kikiPath']
    else:
        kikipath = os.path.join(os.path.dirname(sys.argv[0]), "framework/contributions")
    Kiki.icon(kikipath)
    Kiki.Show(1)
    return Kiki
    
def main():
    global settings
    settings = Settings(dirname=".kiki", filename="kikicfg.py", debugfile="kikidebug")        
    Kiki = wx.PySimpleApp()
    wx.InitAllImageHandlers()
    mw = MyFrameWithEvents(None, -1, "")
    mw.icon()
    Kiki.SetTopWindow(mw)
    mw.Show(1)
    Kiki.MainLoop()

if __name__ == "__main__":
    main()