import wx
import wx.stc as stc

class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.SetSize((950, 750))
        self.SetTitle("Find Text")
        self.main_panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.stc = stc.StyledTextCtrl(self.main_panel, wx.ID_ANY)
        main_sizer.Add(self.stc, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 4)

        middle_sizer = wx.BoxSizer(wx.HORIZONTAL)
        search_for_label = wx.StaticText(self.main_panel, wx.ID_ANY, "Search For:")
        middle_sizer.Add(search_for_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 2)
        self.search_for_text_ctrl = wx.TextCtrl(self.main_panel, wx.ID_ANY, "", style=wx.TE_PROCESS_ENTER)
        middle_sizer.Add(self.search_for_text_ctrl, 1, wx.EXPAND, 0)
        main_sizer.Add(middle_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, 8)

        bottom_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.regex_toggle_button = wx.ToggleButton(self.main_panel, wx.ID_ANY, ".*")
        self.regex_toggle_button.SetMinSize((30, 30))
        self.regex_toggle_button.SetToolTip("Regular Expressions")
        bottom_sizer.Add(self.regex_toggle_button, 0, wx.RIGHT, 8)
        self.case_sensitive_toggle_button = wx.ToggleButton(self.main_panel, wx.ID_ANY, "Aa")
        self.case_sensitive_toggle_button.SetMinSize((30, 30))
        self.case_sensitive_toggle_button.SetToolTip("Case Sensitive")
        bottom_sizer.Add(self.case_sensitive_toggle_button, 0, wx.RIGHT, 8)
        self.whole_word_toggle_button = wx.ToggleButton(self.main_panel, wx.ID_ANY, "W")
        self.whole_word_toggle_button.SetMinSize((30, 30))
        self.whole_word_toggle_button.SetToolTip("Whole Words")
        bottom_sizer.Add(self.whole_word_toggle_button, 0, wx.RIGHT, 24)
        self.find_next_button = wx.Button(self.main_panel, wx.ID_ANY, "Next")
        self.find_next_button.SetToolTip("Find Next")
        self.find_next_button.SetSize(self.find_next_button.GetBestSize())
        bottom_sizer.Add(self.find_next_button, 0, wx.RIGHT, 8)
        self.find_previous_button = wx.Button(self.main_panel, wx.ID_ANY, "Previous")
        self.find_previous_button.SetToolTip("Find Previous")
        self.find_previous_button.SetSize(self.find_previous_button.GetBestSize())
        bottom_sizer.Add(self.find_previous_button, 0, 0, 0)
        main_sizer.Add(bottom_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM | wx.TOP, 8)

        self.main_panel.SetSizer(main_sizer)
        self.Layout()

        self._configureSTC()
        self._loadFile()

        self.last_find_direction = wx.FORWARD

        self.Bind(wx.EVT_BUTTON, self.OnFindNext, self.find_next_button)
        self.Bind(wx.EVT_BUTTON, self.OnFindPrev, self.find_previous_button)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnFindEnter, self.search_for_text_ctrl)

        self.Bind(wx.EVT_MENU, self.OnFind, id=wx.ID_FIND)
        acc_table = wx.AcceleratorTable([(wx.ACCEL_CTRL, ord('F'), wx.ID_FIND)])
        self.SetAcceleratorTable(acc_table)


    def _configureSTC(self):
        self.stc.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
        self.stc.SetMarginMask(1, 0)
        self.stc.SetMarginWidth(1, 50)
        self.stc.SetMultipleSelection(False)
        self.stc.SetSelBackground(True, wx.YELLOW)
        self.stc.SetUseHorizontalScrollBar(0)
        self.stc.SetWrapMode(wx.stc.STC_WRAP_WORD)
        self.stc.SetMarginLeft(2)
        self.stc.SetMarginRight(2)
        self.stc.SetScrollWidth(1)
        self.stc.SetScrollWidthTracking(True)
        self.stc.SetUseTabs(False)
        self.stc.SetTabWidth(4)


    def _loadFile(self):
        with open("stc_find_text.py", 'r') as i_file:
            text = i_file.read()
        self.stc.SetValue(text)
        self.stc.SetEditable(False)


    def ensureSelectionDirection(self, direction=wx.FORWARD):
        current_pos = self.stc.GetCurrentPos()
        anchor_pos = self.stc.GetAnchor()

        if current_pos == anchor_pos:
            return

        if ((direction == wx.FORWARD and current_pos < anchor_pos) or
            (direction == wx.BACKWARD and current_pos > anchor_pos)):
            # Swap end points
            self.stc.SetAnchor(current_pos)
            self.stc.SetCurrentPos(anchor_pos)


    def findNextInSTC(self):
        text = self.search_for_text_ctrl.GetValue()

        if text:
            self.ensureSelectionDirection(wx.FORWARD)
            flags = self.getFindFlags()

            min_pos = self.stc.GetCurrentPos()
            max_pos = self.stc.GetLastPosition()

            find_start, find_end = self.stc.FindText(min_pos, max_pos, text, flags)
            if find_start >= 0 and find_end >= 0:
                self.stc.SetSelection(find_start, find_end)
                self.stc.EnsureCaretVisible()

            self.last_find_direction = wx.FORWARD


    def findPreviousInSTC(self):
        text = self.search_for_text_ctrl.GetValue()

        if text:
            self.ensureSelectionDirection(wx.BACKWARD)
            flags = self.getFindFlags()

            min_pos = self.stc.GetCurrentPos()
            max_pos = 0

            find_start, find_end = self.stc.FindText(min_pos, max_pos, text, flags)
            if find_start >= 0 and find_end >= 0:
                self.stc.SetAnchor(find_end)
                self.stc.SetCurrentPos(find_start)
                self.stc.EnsureCaretVisible()

            self.last_find_direction = wx.BACKWARD


    def getFindFlags(self):
        flags = 0

        if self.regex_toggle_button.GetValue():
            flags |= stc.STC_FIND_REGEXP

        if self.case_sensitive_toggle_button.GetValue():
            flags |= stc.STC_FIND_MATCHCASE

        if self.whole_word_toggle_button.GetValue():
            flags |= stc.STC_FIND_WHOLEWORD

        return flags


    def OnFind(self, _event):
        self.search_for_text_ctrl.SetFocus()
        self.search_for_text_ctrl.SelectAll()


    def OnFindNext(self, _event):
        self.findNextInSTC()


    def OnFindPrev(self, _event):
        self.findPreviousInSTC()


    def OnFindEnter(self, _event):
        if self.last_find_direction == wx.FORWARD:
            self.findNextInSTC()
        else:
            self.findPreviousInSTC()


if __name__ == "__main__":
    app = wx.App()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()
