//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 16 Sep 08 Andy Frank Creation
//
using concurrent
using gfx
using fwt
using flux
**
** FindBar finds text in the current TextEditor.
**
internal class FindBar : ContentPane, TextEditorSupport
{
//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////
new make(TextEditor editor)
{
this.editor = editor
history := FindHistory.load
findText = Combo() { editable = true }
findText.items = history.find
findText.onFocus.add |->| { caretPos = richText.selectStart }
findText.onBlur.add |->| { updateHistory }
findText.onModify.add |->| { find(null, true, true) }
findText.onKeyDown.add |e|
{
switch (e.key)
{
case Key.esc: hide; editor.richText.focus
case Key.enter: next
}
}
matchCase = Button
{
mode = ButtonMode.check
text = Flux.locale("find.matchCase")
onAction.add |->| { updateHistory; find(null, true, true) }
selected = history.matchCase
}
findPane = InsetPane(4,4,4,4)
{
EdgePane
{
center = GridPane
{
numCols = 5
ConstraintPane { minw=50; maxw=50; Label { text = Flux.locale("find.name") }, },
ConstraintPane { minw=200; maxw=200; findText, },
InsetPane(0,0,0,8) { matchCase, },
ToolBar
{
addCommand(cmdNext)
addCommand(cmdPrev)
},
msg,
}
right = ToolBar { addCommand(cmdHide) }
},
}
replaceText = Combo() { editable = true }
replaceText.items = history.find
if (replaceText.items.size > 1)
replaceText.selectedIndex = 1
replaceText.onKeyDown.add |Event e| { if (e.key == Key.esc) hide }
replaceText.onModify.add |Event e|
{
v := findText.text.size > 0 && replaceText.text.size > 0
cmdReplace.enabled = cmdReplaceAll.enabled = v
}
replacePane = InsetPane(0,4,4,4)
{
GridPane
{
numCols = 3
ConstraintPane { minw=50; maxw=50; Label { text = Flux.locale("replace.name") }, },
ConstraintPane { minw=200; maxw=200; it.add(replaceText) },
InsetPane(0,0,0,8)
{
GridPane
{
numCols = 2
Button { command = cmdReplace; image = null },
Button { command = cmdReplaceAll; image = null },
},
},
},
}
content = EdgePane
{
it.top = BorderPane
{
it.border = Border("1,0,1 $Desktop.sysNormShadow,#000,$Desktop.sysHighlightShadow")
}
it.center = EdgePane
{
it.top = findPane
it.bottom = replacePane
}
}
visible = Actor.locals.get("fluxTest.findBar.show", false)
replacePane.visible = Actor.locals.get("fluxTest.findBar.showReplace", false)
}
//////////////////////////////////////////////////////////////////////////
// Methods
//////////////////////////////////////////////////////////////////////////
**
** Show the FindBar with find only in the parent widget.
**
Void showFind()
{
show(false)
find(null, true, true)
}
**
** Show the FindBar with find and replace in the parent widget.
**
Void showFindReplace()
{
show(true)
find(null, true, true)
}
private Void show(Bool showReplace := false)
{
Actor.locals["fluxTest.findBar.show"] = true
Actor.locals["fluxTest.findBar.showReplace"] = showReplace
ignore = true
oldVisible := visible
visible = true
replacePane.visible = showReplace
parent?.parent?.parent?.relayout
// use current selection if it exists
cur := richText.selectText
if (cur.size > 0) findText.text = cur
// make sure text is focued and selected
findText.focus
//findText.selectAll
// if text empty, make sure prev/next disabled
if (findText.text.size == 0)
{
cmdPrev.enabled = false
cmdNext.enabled = false
}
// clear any old msg text
setMsg("")
ignore = false
}
**
** Hide the FindBar in the parent widget.
**
Void hide()
{
Actor.locals["fluxTest.findBar.show"] = false
visible = false
parent?.parent?.parent?.relayout
}
//////////////////////////////////////////////////////////////////////////
// Support
//////////////////////////////////////////////////////////////////////////
**
** Find the current query string in the text document,
** starting at the given caret pos. If pos is null,
** the caretPos recorded when the FindBar was focued
** will be used. If forward is false, the document
** is searched backwards starting at pos. If calcTotal
** is true, the document is searched for the total
** number of occurances of the query string.
**
internal Void find(Int? fromPos, Bool forward := true, Bool calcTotal := false)
{
if (!visible || ignore) return
enabled := false
try
{
q := findText.text
if (q.size == 0)
{
setMsg("")
return
}
enabled = true
match := matchCase.selected
pos := fromPos ?: caretPos
off := forward ?
doc.findNext(q, pos, match) :
doc.findPrev(q, pos-q.size-1, match)
// find total matches
if (calcTotal)
{
total = 0
Int? temp := 0
while ((temp = doc.findNext(q, temp, match)) != null) { total++; temp++ }
}
matchStr := msgTotal
// if found select next occurance
if (off != null)
{
richText.select(off, q.size)
setMsg(matchStr)
return
}
// if not found, try from beginning of file
if (pos > 0 && forward)
{
off = doc.findNext(q, 0, match)
if (off != null)
{
richText.select(off, q.size)
setMsg("$matchStr - " + Flux.locale("find.wrapToTop"))
return
}
}
// if not found, try from end of file
if (pos < doc.size && !forward)
{
off = doc.findPrev(q, doc.size, match)
if (off != null)
{
richText.select(off, q.size)
setMsg("$matchStr - " + Flux.locale("find.wrapToBottom"))
return
}
}
// not found
richText.selectClear
setMsg(Flux.locale("find.notFound"))
enabled = false
}
finally
{
replaceEnabled := enabled && replaceText.text.size > 0
cmdPrev.enabled = enabled
cmdNext.enabled = enabled
cmdReplace.enabled = replaceEnabled
cmdReplaceAll.enabled = replaceEnabled
}
}
**
** Find the next occurance of the query string starting
** at the current caretPos.
**
internal Void next()
{
updateHistory
if (!visible) show
find(richText.caretOffset)
}
**
** Find the previous occurance of the query string starting
** at the current caretPos.
**
internal Void prev()
{
updateHistory
if (!visible) show
find(richText.caretOffset, false)
}
**
** Replace the current query string with the replace string.
**
internal Void replace()
{
updateHistory
newText := replaceText.text
start := richText.selectStart
len := richText.selectSize
richText.modify(start, len, newText)
richText.select(start, newText.size)
total--
if (total > 0) setMsg(msgTotal)
else
{
cmdPrev.enabled = false
cmdNext.enabled = false
cmdReplace.enabled = false
cmdReplaceAll.enabled = false
setMsg(Flux.locale("find.notFound"))
}
}
**
** Replace all occurences of the current query string with
** the replace string.
**
internal Void replaceAll()
{
updateHistory
query := findText.text
replace := replaceText.text
match := matchCase.selected
pos := 0
off := doc.findNext(query, pos, match)
while (off != null)
{
richText.modify(off, query.size, replace)
pos = off + replace.size
off = doc.findNext(query, pos, match)
}
cmdPrev.enabled = false
cmdNext.enabled = false
cmdReplace.enabled = false
cmdReplaceAll.enabled = false
setMsg(Flux.locale("find.notFound"))
}
private Void setMsg(Str text)
{
msg.text = text
msg.parent.relayout
}
private Str msgTotal()
{
return total == 1
? "1 " + Flux.locale("find.match")
: "$total " + Flux.locale("find.matches")
}
private Void updateHistory()
{
// save history
history := FindHistory.load
if (replacePane.visible)
history.pushFind(replaceText.text)
history.pushFind(findText.text)
history.matchCase = matchCase.selected
history.save
// update ui
updateCombo(findText)
updateCombo(replaceText)
}
private Void updateCombo(Combo c)
{
text := c.text
if (text.size == 0) return
if (text == c.items.first) return
// bubble text to top
ignore = true
items := c.items.dup
items.remove(text)
items.insert(0, text)
// update combo, limit items
c.items = items[0..<20.min(items.size)]
c.text = text // set items nukes text, so reset
ignore = false
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
override TextEditor editor { private set }
private Int caretPos
private Widget findPane
private Widget replacePane
private Combo findText
private Combo replaceText
private Button matchCase
private Int total
private Label msg := Label()
private Bool ignore := false
private Command cmdNext := Command.makeLocale(Flux#.pod, "findPrev") { prev }
private Command cmdPrev := Command.makeLocale(Flux#.pod, "findNext") { next }
private Command cmdHide := Command.makeLocale(Flux#.pod, "findHide") { hide }
private Command cmdReplace := Command.makeLocale(Flux#.pod, "replace") { replace }
private Command cmdReplaceAll := Command.makeLocale(Flux#.pod, "replaceAll") { replaceAll }
}