438 lines
13 KiB
Python
438 lines
13 KiB
Python
import config
|
|
import gutil
|
|
import misc
|
|
import undo
|
|
import util
|
|
|
|
import wx
|
|
|
|
class FindDlg(wx.Dialog):
|
|
def __init__(self, parent, ctrl):
|
|
wx.Dialog.__init__(self, parent, -1, "Find & Replace",
|
|
style = wx.DEFAULT_DIALOG_STYLE | wx.WANTS_CHARS)
|
|
|
|
self.ctrl = ctrl
|
|
|
|
self.searchLine = -1
|
|
self.searchColumn = -1
|
|
self.searchWidth = -1
|
|
|
|
hsizer = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
vsizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
gsizer = wx.FlexGridSizer(2, 2, 5, 20)
|
|
gsizer.AddGrowableCol(1)
|
|
|
|
gsizer.Add(wx.StaticText(self, -1, "Find what:"), 0,
|
|
wx.ALIGN_CENTER_VERTICAL)
|
|
self.findEntry = wx.TextCtrl(self, -1, style = wx.TE_PROCESS_ENTER)
|
|
gsizer.Add(self.findEntry, 0, wx.EXPAND)
|
|
|
|
gsizer.Add(wx.StaticText(self, -1, "Replace with:"), 0,
|
|
wx.ALIGN_CENTER_VERTICAL)
|
|
self.replaceEntry = wx.TextCtrl(self, -1, style = wx.TE_PROCESS_ENTER)
|
|
gsizer.Add(self.replaceEntry, 0, wx.EXPAND)
|
|
|
|
vsizer.Add(gsizer, 0, wx.EXPAND | wx.BOTTOM, 10)
|
|
|
|
hsizer2 = wx.BoxSizer(wx.HORIZONTAL)
|
|
|
|
vsizer2 = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
# wxGTK adds way more space by default than wxMSW between the
|
|
# items, have to adjust for that
|
|
pad = 0
|
|
if misc.isWindows:
|
|
pad = 5
|
|
|
|
self.matchWholeCb = wx.CheckBox(self, -1, "Match whole word only")
|
|
vsizer2.Add(self.matchWholeCb, 0, wx.TOP, pad)
|
|
|
|
self.matchCaseCb = wx.CheckBox(self, -1, "Match case")
|
|
vsizer2.Add(self.matchCaseCb, 0, wx.TOP, pad)
|
|
|
|
hsizer2.Add(vsizer2, 0, wx.EXPAND | wx.RIGHT, 10)
|
|
|
|
self.direction = wx.RadioBox(self, -1, "Direction",
|
|
choices = ["Up", "Down"])
|
|
self.direction.SetSelection(1)
|
|
|
|
hsizer2.Add(self.direction, 1, 0)
|
|
|
|
vsizer.Add(hsizer2, 0, wx.EXPAND | wx.BOTTOM, 10)
|
|
|
|
self.extraLabel = wx.StaticText(self, -1, "Search in:")
|
|
vsizer.Add(self.extraLabel)
|
|
|
|
self.elements = wx.CheckListBox(self, -1)
|
|
|
|
# sucky wxMSW doesn't support client data for checklistbox items,
|
|
# so we have to store it ourselves
|
|
self.elementTypes = []
|
|
|
|
for t in config.getTIs():
|
|
self.elements.Append(t.name)
|
|
self.elementTypes.append(t.lt)
|
|
|
|
vsizer.Add(self.elements, 1, wx.EXPAND)
|
|
|
|
hsizer.Add(vsizer, 1, wx.EXPAND)
|
|
|
|
vsizer = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
find = wx.Button(self, -1, "&Find next")
|
|
vsizer.Add(find, 0, wx.EXPAND | wx.BOTTOM, 5)
|
|
|
|
replace = wx.Button(self, -1, "&Replace")
|
|
vsizer.Add(replace, 0, wx.EXPAND | wx.BOTTOM, 5)
|
|
|
|
replaceAll = wx.Button(self, -1, " Replace all ")
|
|
vsizer.Add(replaceAll, 0, wx.EXPAND | wx.BOTTOM, 5)
|
|
|
|
self.moreButton = wx.Button(self, -1, "")
|
|
vsizer.Add(self.moreButton, 0, wx.EXPAND | wx.BOTTOM, 5)
|
|
|
|
hsizer.Add(vsizer, 0, wx.EXPAND | wx.LEFT, 30)
|
|
|
|
self.Bind(wx.EVT_BUTTON, self.OnFind, id=find.GetId())
|
|
self.Bind(wx.EVT_BUTTON, self.OnReplace, id=replace.GetId())
|
|
self.Bind(wx.EVT_BUTTON, self.OnReplaceAll, id=replaceAll.GetId())
|
|
self.Bind(wx.EVT_BUTTON, self.OnMore, id=self.moreButton.GetId())
|
|
|
|
gutil.btnDblClick(find, self.OnFind)
|
|
gutil.btnDblClick(replace, self.OnReplace)
|
|
|
|
self.Bind(wx.EVT_TEXT, self.OnText, id=self.findEntry.GetId())
|
|
|
|
self.Bind(wx.EVT_TEXT_ENTER, self.OnFind, id=self.findEntry.GetId())
|
|
self.Bind(wx.EVT_TEXT_ENTER, self.OnFind, id=self.replaceEntry.GetId())
|
|
|
|
self.Bind(wx.EVT_CHAR, self.OnCharMisc)
|
|
self.findEntry.Bind(wx.EVT_CHAR, self.OnCharEntry)
|
|
self.replaceEntry.Bind(wx.EVT_CHAR, self.OnCharEntry)
|
|
find.Bind(wx.EVT_CHAR, self.OnCharButton)
|
|
replace.Bind(wx.EVT_CHAR, self.OnCharButton)
|
|
replaceAll.Bind(wx.EVT_CHAR, self.OnCharButton)
|
|
self.moreButton.Bind(wx.EVT_CHAR, self.OnCharButton)
|
|
self.matchWholeCb.Bind(wx.EVT_CHAR, self.OnCharMisc)
|
|
self.matchCaseCb.Bind(wx.EVT_CHAR, self.OnCharMisc)
|
|
self.direction.Bind(wx.EVT_CHAR, self.OnCharMisc)
|
|
self.elements.Bind(wx.EVT_CHAR, self.OnCharMisc)
|
|
|
|
util.finishWindow(self, hsizer, center = False)
|
|
|
|
self.loadState()
|
|
self.findEntry.SetFocus()
|
|
|
|
def loadState(self):
|
|
self.findEntry.SetValue(self.ctrl.findDlgFindText)
|
|
self.findEntry.SetSelection(-1, -1)
|
|
|
|
self.replaceEntry.SetValue(self.ctrl.findDlgReplaceText)
|
|
|
|
self.matchWholeCb.SetValue(self.ctrl.findDlgMatchWholeWord)
|
|
self.matchCaseCb.SetValue(self.ctrl.findDlgMatchCase)
|
|
|
|
self.direction.SetSelection(int(not self.ctrl.findDlgDirUp))
|
|
|
|
count = self.elements.GetCount()
|
|
tmp = self.ctrl.findDlgElements
|
|
|
|
if (tmp == None) or (len(tmp) != count):
|
|
tmp = [True] * self.elements.GetCount()
|
|
|
|
for i in range(count):
|
|
self.elements.Check(i, tmp[i])
|
|
|
|
self.showExtra(self.ctrl.findDlgUseExtra)
|
|
self.Center()
|
|
|
|
def saveState(self):
|
|
self.getParams()
|
|
|
|
self.ctrl.findDlgFindText = misc.fromGUI(self.findEntry.GetValue())
|
|
self.ctrl.findDlgReplaceText = misc.fromGUI(
|
|
self.replaceEntry.GetValue())
|
|
self.ctrl.findDlgMatchWholeWord = self.matchWhole
|
|
self.ctrl.findDlgMatchCase = self.matchCase
|
|
self.ctrl.findDlgDirUp = self.dirUp
|
|
self.ctrl.findDlgUseExtra = self.useExtra
|
|
|
|
tmp = []
|
|
for i in range(self.elements.GetCount()):
|
|
tmp.append(bool(self.elements.IsChecked(i)))
|
|
|
|
self.ctrl.findDlgElements = tmp
|
|
|
|
def OnMore(self, event):
|
|
self.showExtra(not self.useExtra)
|
|
|
|
def OnText(self, event):
|
|
if self.ctrl.sp.mark:
|
|
self.ctrl.sp.clearMark()
|
|
self.ctrl.updateScreen()
|
|
|
|
def OnCharEntry(self, event):
|
|
self.OnChar(event, True, False)
|
|
|
|
def OnCharButton(self, event):
|
|
self.OnChar(event, False, True)
|
|
|
|
def OnCharMisc(self, event):
|
|
self.OnChar(event, False, False)
|
|
|
|
def OnChar(self, event, isEntry, isButton):
|
|
kc = event.GetKeyCode()
|
|
|
|
if kc == wx.WXK_ESCAPE:
|
|
self.EndModal(wx.ID_OK)
|
|
return
|
|
|
|
if kc == wx.WXK_RETURN:
|
|
if isButton:
|
|
event.Skip()
|
|
return
|
|
else:
|
|
self.OnFind()
|
|
return
|
|
|
|
if isEntry:
|
|
event.Skip()
|
|
else:
|
|
if kc < 256:
|
|
if chr(kc) == "f":
|
|
self.OnFind()
|
|
elif chr(kc) == "r":
|
|
self.OnReplace()
|
|
else:
|
|
event.Skip()
|
|
else:
|
|
event.Skip()
|
|
|
|
def showExtra(self, flag):
|
|
self.extraLabel.Show(flag)
|
|
self.elements.Show(flag)
|
|
|
|
self.useExtra = flag
|
|
|
|
if flag:
|
|
self.moreButton.SetLabel("<<< Less")
|
|
pos = self.elements.GetPosition()
|
|
|
|
# don't know of a way to get the vertical spacing of items in
|
|
# a wx.CheckListBox, so estimate it at font height + 5 pixels,
|
|
# which is close enough on everything I've tested.
|
|
h = pos.y + len(self.elementTypes) * \
|
|
(util.getFontHeight(self.elements.GetFont()) + 5) + 15
|
|
else:
|
|
self.moreButton.SetLabel("More >>>")
|
|
h = max(self.extraLabel.GetPosition().y,
|
|
self.moreButton.GetPosition().y +
|
|
self.moreButton.GetClientSize().height + 5)
|
|
|
|
self.SetSizeHints(self.GetClientSize().width, h)
|
|
util.setWH(self, h = h)
|
|
|
|
def getParams(self):
|
|
self.dirUp = self.direction.GetSelection() == 0
|
|
self.matchWhole = self.matchWholeCb.IsChecked()
|
|
self.matchCase = self.matchCaseCb.IsChecked()
|
|
|
|
if self.useExtra:
|
|
self.elementMap = {}
|
|
for i in range(self.elements.GetCount()):
|
|
self.elementMap[self.elementTypes[i]] = \
|
|
self.elements.IsChecked(i)
|
|
|
|
def typeIncluded(self, lt):
|
|
if not self.useExtra:
|
|
return True
|
|
|
|
return self.elementMap[lt]
|
|
|
|
def OnFind(self, event = None, autoFind = False):
|
|
if not autoFind:
|
|
self.getParams()
|
|
|
|
value = misc.fromGUI(self.findEntry.GetValue())
|
|
if not self.matchCase:
|
|
value = util.upper(value)
|
|
|
|
if value == "":
|
|
return
|
|
|
|
self.searchWidth = len(value)
|
|
|
|
if self.dirUp:
|
|
inc = -1
|
|
else:
|
|
inc = 1
|
|
|
|
line = self.ctrl.sp.line
|
|
col = self.ctrl.sp.column
|
|
ls = self.ctrl.sp.lines
|
|
|
|
if (line == self.searchLine) and (col == self.searchColumn):
|
|
text = ls[line].text
|
|
|
|
col += inc
|
|
if col >= len(text):
|
|
line += 1
|
|
col = 0
|
|
elif col < 0:
|
|
line -= 1
|
|
if line >= 0:
|
|
col = max(len(ls[line].text) - 1, 0)
|
|
|
|
fullSearch = False
|
|
if inc > 0:
|
|
if (line == 0) and (col == 0):
|
|
fullSearch = True
|
|
else:
|
|
if (line == (len(ls) - 1)) and (col == (len(ls[line].text))):
|
|
fullSearch = True
|
|
|
|
self.searchLine = -1
|
|
|
|
while True:
|
|
found = False
|
|
|
|
while True:
|
|
if (line >= len(ls)) or (line < 0):
|
|
break
|
|
|
|
if self.typeIncluded(ls[line].lt):
|
|
text = ls[line].text
|
|
value = str(value)
|
|
if not self.matchCase:
|
|
text = util.upper(text)
|
|
|
|
if inc > 0:
|
|
res = text.find(value, col)
|
|
else:
|
|
res = text.rfind(value, 0, col + 1)
|
|
|
|
if res != -1:
|
|
if not self.matchWhole or (
|
|
util.isWordBoundary(text[res - 1 : res]) and
|
|
util.isWordBoundary(text[res + len(value) :
|
|
res + len(value) + 1])):
|
|
|
|
found = True
|
|
|
|
break
|
|
|
|
line += inc
|
|
if inc > 0:
|
|
col = 0
|
|
else:
|
|
if line >= 0:
|
|
col = max(len(ls[line].text) - 1, 0)
|
|
|
|
if found:
|
|
self.searchLine = line
|
|
self.searchColumn = res
|
|
self.ctrl.sp.gotoPos(line, res)
|
|
self.ctrl.sp.setMark(line, res + self.searchWidth - 1)
|
|
|
|
if not autoFind:
|
|
self.ctrl.makeLineVisible(line)
|
|
self.ctrl.updateScreen()
|
|
|
|
break
|
|
else:
|
|
if autoFind:
|
|
break
|
|
|
|
if fullSearch:
|
|
wx.MessageBox("Search finished without results.",
|
|
"No matches", wx.OK, self)
|
|
|
|
break
|
|
|
|
if inc > 0:
|
|
s1 = "end"
|
|
s2 = "start"
|
|
restart = 0
|
|
else:
|
|
s1 = "start"
|
|
s2 = "end"
|
|
restart = len(ls) - 1
|
|
|
|
if wx.MessageBox("Search finished at the %s of the script. Do\n"
|
|
"you want to continue at the %s of the script?"
|
|
% (s1, s2), "Continue?",
|
|
wx.YES_NO | wx.YES_DEFAULT, self) == wx.YES:
|
|
line = restart
|
|
fullSearch = True
|
|
else:
|
|
break
|
|
|
|
if not autoFind:
|
|
self.ctrl.updateScreen()
|
|
|
|
def OnReplace(self, event = None, autoFind = False):
|
|
if self.searchLine == -1:
|
|
return False
|
|
|
|
value = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue()))
|
|
ls = self.ctrl.sp.lines
|
|
|
|
sp = self.ctrl.sp
|
|
u = undo.SinglePara(sp, undo.CMD_MISC, self.searchLine)
|
|
|
|
ls[self.searchLine].text = util.replace(
|
|
ls[self.searchLine].text, value,
|
|
self.searchColumn, self.searchWidth)
|
|
|
|
sp.rewrapPara(sp.getParaFirstIndexFromLine(self.searchLine))
|
|
|
|
self.searchLine = -1
|
|
|
|
diff = len(value) - self.searchWidth
|
|
|
|
if not self.dirUp:
|
|
sp.column += self.searchWidth + diff
|
|
else:
|
|
sp.column -= 1
|
|
|
|
if sp.column < 0:
|
|
sp.line -= 1
|
|
|
|
if sp.line < 0:
|
|
sp.line = 0
|
|
sp.column = 0
|
|
|
|
self.searchLine = 0
|
|
self.searchColumn = 0
|
|
self.searchWidth = 0
|
|
else:
|
|
sp.column = len(ls[sp.line].text)
|
|
|
|
sp.clearMark()
|
|
sp.markChanged()
|
|
|
|
u.setAfter(sp)
|
|
sp.addUndo(u)
|
|
|
|
self.OnFind(autoFind = autoFind)
|
|
|
|
return True
|
|
|
|
def OnReplaceAll(self, event = None):
|
|
self.getParams()
|
|
|
|
if self.searchLine == -1:
|
|
self.OnFind(autoFind = True)
|
|
|
|
count = 0
|
|
while self.OnReplace(autoFind = True):
|
|
count += 1
|
|
|
|
if count != 0:
|
|
self.ctrl.makeLineVisible(self.ctrl.sp.line)
|
|
self.ctrl.updateScreen()
|
|
|
|
wx.MessageBox("Replaced %d matches" % count, "Results", wx.OK, self)
|