#! /usr/bin/env fan
//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 28 Jul 08 Brian Frank Creation
//
using gfx
using fwt
**
** RichTextDemo illustrates how to use the RichText widget
**
class RichTextDemo
{
Void main()
{
Doc? doc := Doc()
doc.text = scriptFile.readAllLines.join("\n")
//doc.text = "hello // world\n//what the heck!\nalpha beta gamma."
//doc.log.level = LogLevel.debug
Window
{
title = "RichText Demo"
InsetPane
{
RichText
{
model=doc
font = doc.defFont
//hbar.onModify.add |e| { onScroll("hbar", e) }
//vbar.onModify.add |e| { onScroll("vbar", e) }
//onVerify.add |e| { echo("verify: $e.data") }
//onVerifyKey.add |e| { echo("verify: $e") }
//onSelect.add |e| { echo(e) }
},
},;
size = Size(600,600)
}.open
}
static Void onScroll(Str name, Event e)
{
ScrollBar sb := e.widget
echo("-- onScroll $name $e [val=$sb.val min=$sb.min max=$sb.max thumb=$sb.thumb page=$sb.page orient=$sb.orientation")
}
File scriptFile := this.typeof->sourceFile.toStr.toUri.toFile
}
**
** This class provides a grossly inefficient implementation
** for managing a document. But should be easy to understand.
**
class Doc : RichTextModel
{
override Str text := ""
const Log log := Log.get("Doc")
override Int charCount()
{
r := text.size
log.debug("charCount => $r")
return r
}
override Int lineCount()
{
r := text.splitLines.size
log.debug("lineCount => $r")
return r
}
override Str line(Int lineIndex)
{
r := text.splitLines[lineIndex]
log.debug("line($lineIndex) => $r")
return r
}
override Int lineAtOffset(Int offset)
{
line := 0
for (i:=0; i<offset; ++i) if (text[i] == '\n') line++
log.debug("lineAtOffset($offset) => $line")
return line
}
override Int offsetAtLine(Int lineIndex)
{
Int r := text.splitLines[0..<lineIndex]
.reduce(0) |Int o, Str line->Int| { return line.size+o+1 }
log.debug("offsetAtLine($lineIndex) => $r")
return r
}
override Str textRange(Int start, Int len)
{
r := text[start..<start+len]
log.debug("textRange($start, $len) => $r.toCode")
return r
}
override Void modify(Int start, Int len, Str newText)
{
log.debug("modify($start, $len, $newText)")
// update model
oldText := textRange(start, len)
text = text[0..<start] + newText + text[start+len..-1]
// must fire modify event
tc := TextChange
{
it.startOffset = start
it.startLine = lineAtOffset(start)
it.oldText = oldText
it.newText = newText
it.oldNumNewlines = oldText.numNewlines
it.newNumNewlines = newText.numNewlines
}
onModify.fire(Event { id = EventId.modified; data = tc })
}
override Obj[]? lineStyling(Int lineIndex)
{
// style { or } using brace color,
// and // or ** as end of line comments
line := line(lineIndex)
styles := Obj[,]
inComment := false
last := 0
line.each |Int ch, Int i|
{
if (inComment) return
if (ch == '{' || ch == '}')
{ styles.add(i).add(brace).add(i+1).add(normal) }
else if (ch == '/' && last == '/')
{ styles.add(i-1).add(comment); inComment = true }
else if (ch == '*' && last == '*')
{ styles.add(i-1).add(comment); inComment = true }
last = ch
}
if (styles.first != 0) styles.insert(0, 0).insert(1, normal)
return styles
}
Font defFont := Font { name="Courier New"; size=9 }
RichTextStyle normal := RichTextStyle { font=defFont }
RichTextStyle brace := RichTextStyle { font=defFont; fg=Color.red }
RichTextStyle comment := RichTextStyle { font=defFont; fg=Color.make(0x00_7f_00) }
}