// NewEditField creates a new EditField control // view - is a View that manages the control // parent - is container that keeps the control. The same View can be a view and a parent at the same time. // width - is minimal width of the control. // text - text to edit. // scale - the way of scaling the control when the parent is resized. Use DoNotScale constant if the // control should keep its original size. func CreateEditField(parent Control, width int, text string, scale int) *EditField { e := new(EditField) e.onChange = nil e.SetTitle(text) e.SetEnabled(true) if width == AutoSize { width = xs.Len(text) + 1 } e.SetSize(width, 1) e.cursorPos = xs.Len(text) e.offset = 0 e.parent = parent e.parent = parent e.readonly = false e.SetScale(scale) e.SetConstraints(width, 1) e.end() if parent != nil { parent.AddChild(e) } return e }
func (e *EditField) insertRune(ch rune) { if e.readonly { return } if e.maxWidth > 0 && xs.Len(e.title) >= e.maxWidth { return } idx := e.cursorPos if idx == 0 { e.SetTitle(string(ch) + e.title) } else if idx >= xs.Len(e.title) { e.SetTitle(e.title + string(ch)) } else { e.SetTitle(xs.Slice(e.title, 0, idx) + string(ch) + xs.Slice(e.title, idx, -1)) } e.cursorPos++ if e.cursorPos >= e.width { if e.offset == 0 { e.offset = 2 } else { e.offset++ } } }
func enginePost(blogPosts map[string]*BlogPost, href string, whenDone chan bool) { doc, err := goquery.NewDocument(appConfiguration["website_root"].StringValue + href) if err != nil { fmt.Printf("Impossible to reach %s\n", href) whenDone <- false return } author := strings.Trim(doc.Find(".james-author").Text(), " \n") headline := strings.Trim(doc.Find(".james-headline").Text(), " \n") text := doc.Find(".james-content").Text() timestamp, _ := doc.Find(".james-date").Attr("data-timestamp") parsedTime, _ := strconv.ParseInt(timestamp, 10, 64) hash := make(map[string]int) post := BlogPost{ href, author, headline, time.Unix(parsedTime, 0), hash, "", } buffer := "" words := 0 for i := 0; i < xstrings.Len(text); i++ { c := string(text[i]) if isMatching, _ := regexp.MatchString("[a-zA-Z0-9]", c); isMatching == true { buffer += c } else if buffer != "" { if xstrings.Len(buffer) >= appConfiguration["min_word_length"].IntValue { buffer = strings.ToLower(buffer) e, exists := hash[buffer] if exists { hash[buffer] = e + 1 } else { hash[buffer] = 1 } } if words < appConfiguration["brief_number_of_words"].IntValue { if words > 0 { post.Brief += " " } post.Brief += buffer words++ } buffer = "" } } blogPosts[href] = &post whenDone <- true }
// Repaint draws the control on its View surface func (e *EditField) Draw() { PushAttributes() defer PopAttributes() x, y := e.Pos() w, _ := e.Size() parts := []rune(SysObject(ObjEdit)) chLeft, chRight := string(parts[0]), string(parts[1]) var textOut string curOff := 0 if e.offset == 0 && xs.Len(e.title) < e.width { textOut = e.title } else { fromIdx := 0 toIdx := 0 if e.offset == 0 { toIdx = e.width - 1 textOut = xs.Slice(e.title, 0, toIdx) + chRight curOff = -e.offset } else { curOff = 1 - e.offset fromIdx = e.offset if e.width-1 <= xs.Len(e.title)-e.offset { toIdx = e.offset + e.width - 2 textOut = chLeft + xs.Slice(e.title, fromIdx, toIdx) + chRight } else { textOut = chLeft + xs.Slice(e.title, fromIdx, -1) } } } fg, bg := RealColor(e.fg, ColorEditText), RealColor(e.bg, ColorEditBack) if !e.Enabled() { fg, bg = RealColor(e.fg, ColorDisabledText), RealColor(e.fg, ColorDisabledBack) } else if e.Active() { fg, bg = RealColor(e.fg, ColorEditActiveText), RealColor(e.bg, ColorEditActiveBack) } SetTextColor(fg) SetBackColor(bg) FillRect(x, y, w, 1, ' ') DrawRawText(x, y, textOut) if e.Active() { SetCursorPos(e.cursorPos+e.x+curOff, e.y) } }
func (l *TextView) calculateVirtualSize() { w := l.width - 1 l.virtualWidth = l.width - 1 l.virtualHeight = 0 l.lengths = make([]int, len(l.lines)) for idx, str := range l.lines { str = UnColorizeText(str) sz := xs.Len(str) if l.wordWrap { n := sz / w r := sz % w l.virtualHeight += n if r != 0 { l.virtualHeight++ } } else { l.virtualHeight++ if sz > l.virtualWidth { l.virtualWidth = sz } } l.lengths[idx] = sz } }
/* NewButton creates a new Button. view - is a View that manages the control parent - is container that keeps the control. The same View can be a view and a parent at the same time. width and heigth - are minimal size of the control. title - button title. scale - the way of scaling the control when the parent is resized. Use DoNotScale constant if the control should keep its original size. */ func CreateButton(parent Control, width, height int, title string, scale int) *Button { b := new(Button) b.parent = parent b.align = AlignCenter if height == AutoSize { height = 4 } if width == AutoSize { width = xs.Len(title) + 2 + 1 } if height < 4 { height = 4 } if width < 6 { width = 6 } b.SetTitle(title) b.SetSize(width, height) b.SetConstraints(width, height) b.SetScale(scale) if parent != nil { parent.AddChild(b) } return b }
// Repaint draws the control on its View surface func (f *Frame) Draw() { PushAttributes() defer PopAttributes() if f.border == BorderNone { f.DrawChildren() return } x, y := f.Pos() w, h := f.Size() fg, bg := RealColor(f.fg, ColorViewText), RealColor(f.bg, ColorViewBack) SetTextColor(fg) SetBackColor(bg) DrawFrame(x, y, w, h, f.border) if f.title != "" { str := f.title raw := UnColorizeText(str) if xs.Len(raw) > w-2 { str = SliceColorized(str, 0, w-2-3) + "..." } DrawText(x+1, y, str) } f.DrawChildren() }
// SetMaxWidth sets the maximum lenght of the EditField text. If the current text is longer it is truncated func (e *EditField) SetMaxWidth(w int) { e.maxWidth = w if w > 0 && xs.Len(e.title) > w { e.title = xs.Slice(e.title, 0, w) e.end() } }
// CutText makes a text no longer than maxWidth func CutText(str string, maxWidth int) string { ln := xs.Len(str) if ln <= maxWidth { return str } return xs.Slice(str, 0, maxWidth) }
func (e *EditField) end() { length := xs.Len(e.title) e.cursorPos = length if length < e.width { return } e.offset = length - (e.width - 2) }
// AlignColorizedText does the same as AlignText does but // it preserves the color of the letters by adding correct // color tags to the line beginning. // Note: function is ineffective and a bit slow - do not use // it everywhere func AlignColorizedText(str string, width int, align Align) (int, string) { rawText := UnColorizeText(str) length := xs.Len(rawText) if length <= width { shift, _ := AlignText(rawText, width, align) return shift, str } skip := 0 if align == AlignRight { skip = length - width } else if align == AlignCenter { skip = (length - width) / 2 } fgChanged, bgChanged := false, false curr := 0 parser := NewColorParser(str, term.ColorBlack, term.ColorBlack) out := "" for curr < skip+width { elem := parser.NextElement() if elem.Type == ElemEndOfText { break } if elem.Type == ElemPrintable { curr++ if curr == skip+1 { if fgChanged { out += "<t:" + ColorToString(elem.Fg) + ">" } if bgChanged { out += "<b:" + ColorToString(elem.Bg) + ">" } out += string(elem.Ch) } else if curr > skip+1 { out += string(elem.Ch) } } else if elem.Type == ElemTextColor { fgChanged = true if curr > skip+1 { out += "<t:" + ColorToString(elem.Fg) + ">" } } else if elem.Type == ElemBackColor { bgChanged = true if curr > skip+1 { out += "<b:" + ColorToString(elem.Bg) + ">" } } } return 0, out }
func (e *EditField) charRight() { length := xs.Len(e.title) if e.cursorPos == length || e.title == "" { return } e.cursorPos++ if e.cursorPos != length && e.cursorPos >= e.offset+e.width-2 { e.offset++ } }
// Ellipsize truncates text to maxWidth by replacing a // substring in the middle with ellipsis and keeping // the beginning and ending of the string untouched. // If maxWidth is less than 5 then no ellipsis is // added, the text is just truncated from the right. func Ellipsize(str string, maxWidth int) string { ln := xs.Len(str) if ln <= maxWidth { return str } if maxWidth < 5 { return xs.Slice(str, 0, maxWidth) } left := int((maxWidth - 3) / 2) right := maxWidth - left - 3 return xs.Slice(str, 0, left) + "..." + xs.Slice(str, ln-right, -1) }
// AlignText calculates the initial position of the text // output depending on str length and available width. // The str is truncated in case of its lenght greater than // width. Function returns shift that should be added to // original label position before output instead of padding // the string with spaces. The reason is to make possible // to draw a label aligned but with transparent beginning // and ending. If you do not need transparency you can // add spaces manually using the returned shift value func AlignText(str string, width int, align Align) (shift int, out string) { length := xs.Len(str) if length >= width { return 0, CutText(str, width) } if align == AlignRight { return width - length, str } else if align == AlignCenter { return (width - length) / 2, str } return 0, str }
func compareWords(input string, reference string) float64 { var s int var prev1 string var prev2 string score := 0.0 s1 := xstrings.Len(input) s2 := xstrings.Len(reference) if s1 < s2 { s = s1 } else { s = s2 } for i := 0; i < s; i++ { a := string(input[i]) b := string(reference[i]) if i > 0 { if prev1 == prev2 && a == b { score += 1 } else if prev1 == b && prev2 == a { score += 0.9 } else if prev1 == prev2 || a == b { score += 0.75 } } prev1 = a prev2 = b } score /= float64(s1 - 1) return score }
func (e *EditField) del() { length := xs.Len(e.title) if e.title == "" || e.cursorPos == length || e.readonly { return } if e.cursorPos == length-1 { e.SetTitle(xs.Slice(e.title, 0, length-1)) } else { e.SetTitle(xs.Slice(e.title, 0, e.cursorPos) + xs.Slice(e.title, e.cursorPos+1, -1)) } if length-1 < e.width { e.offset = 0 } }
func (wnd *Window) drawTitle() { PushAttributes() defer PopAttributes() btnCount := wnd.buttonCount() maxw := wnd.width - 2 - btnCount if btnCount > 0 { maxw -= 2 } fitTitle := wnd.title rawText := UnColorizeText(fitTitle) if xs.Len(rawText) > maxw { fitTitle = SliceColorized(fitTitle, 0, maxw-3) + "..." } DrawText(wnd.x+1, wnd.y, fitTitle) }
/* NewRadio creates a new radio button. view - is a View that manages the control parent - is container that keeps the control. The same View can be a view and a parent at the same time. width - is minimal width of the control. title - radio title. scale - the way of scaling the control when the parent is resized. Use DoNotScale constant if the control should keep its original size. */ func CreateRadio(parent Control, width int, title string, scale int) *Radio { c := new(Radio) if width == AutoSize { width = xs.Len(title) + 4 } c.parent = parent c.SetSize(width, 1) // TODO: only one line heigth is supported at that moment c.SetConstraints(width, 1) c.SetTitle(title) c.SetTabStop(true) c.SetScale(scale) if parent != nil { parent.AddChild(c) } return c }
func (e *EditField) backspace() { if e.title == "" || e.cursorPos == 0 || e.readonly { return } length := xs.Len(e.title) if e.cursorPos >= length { e.cursorPos-- e.SetTitle(xs.Slice(e.title, 0, length-1)) } else if e.cursorPos == 1 { e.cursorPos = 0 e.SetTitle(xs.Slice(e.title, 1, -1)) e.offset = 0 } else { e.cursorPos-- e.SetTitle(xs.Slice(e.title, 0, e.cursorPos) + xs.Slice(e.title, e.cursorPos+1, -1)) } if length-1 < e.width { e.offset = 0 } }
/* CreateCheckBox creates a new CheckBox control. parent - is container that keeps the control. The same View can be a view and a parent at the same time. width - is minimal width of the control. title - button title. scale - the way of scaling the control when the parent is resized. Use DoNotScale constant if the control should keep its original size. CheckBox state can be changed using mouse or pressing space on keyboard while the control is active */ func CreateCheckBox(parent Control, width int, title string, scale int) *CheckBox { c := new(CheckBox) c.parent = parent if width == AutoSize { width = xs.Len(title) + 4 } c.SetSize(width, 1) // TODO: only one line checkboxes are supported at that moment c.SetConstraints(width, 1) c.state = 0 c.SetTitle(title) c.SetTabStop(true) c.allow3state = false c.onChange = nil c.SetScale(scale) if parent != nil { parent.AddChild(c) } return c }
/* NewLabel creates a new label. view - is a View that manages the control parent - is container that keeps the control. The same View can be a view and a parent at the same time. w and h - are minimal size of the control. title - is Label title. scale - the way of scaling the control when the parent is resized. Use DoNotScale constant if the control should keep its original size. */ func CreateLabel(parent Control, w, h int, title string, scale int) *Label { c := new(Label) if w == AutoSize { w = xs.Len(title) } if h == AutoSize { h = 1 } c.parent = parent c.SetTitle(title) c.SetSize(w, h) c.SetConstraints(w, h) c.SetScale(scale) c.tabSkip = true if parent != nil { parent.AddChild(c) } return c }
// DrawRawText draws the part of text that is inside the current clipping // rectangle. DrawRawText always paints string as is - no color changes. // If you want to draw string with color changing commands included then // use DrawText function func DrawRawText(x, y int, text string) { cx, cy, cw, ch := ClipRect() if x >= cx+cw || y < cy || y >= cy+ch { return } length := xs.Len(text) if x+length < cx { return } if x < cx { text = xs.Slice(text, cx-x, -1) length = length - (cx - x) x = cx } text = CutText(text, cw) dx := 0 for _, ch := range text { putCharUnsafe(x+dx, y, ch) dx++ } }
// DrawRawTextVertical draws the part of text that is inside the current clipping // rectangle. DrawRawTextVertical always paints string as is - no color changes. // If you want to draw string with color changing commands included then // use DrawTextVertical function func DrawRawTextVertical(x, y int, text string) { cx, cy, cw, ch := ClipRect() if y >= cy+ch || x < cx || x >= cx+cw { return } length := xs.Len(text) if y+length < cy { return } if y < cy { text = xs.Slice(text, cy-y, -1) length = length - (cy - y) y = cy } text = CutText(text, ch) dy := 0 for _, ch := range text { putCharUnsafe(x, y+dy, ch) dy++ } }
// Repaint draws the control on its View surface. // Horizontal ProgressBar supports custom title over the bar. // One can set title using method SetTitle. There are a few // predefined variables that can be used inside title to // show value or total progress. Variable must be enclosed // with double curly brackets. Available variables: // percent - the current percentage rounded to closest integer // value - raw ProgressBar value // min - lower ProgressBar limit // max - upper ProgressBar limit // Examples: // pb.SetTitle("{{value}} of {{max}}") // pb.SetTitle("{{percent}}%") func (b *ProgressBar) Draw() { if b.max <= b.min { return } PushAttributes() defer PopAttributes() fgOff, fgOn := RealColor(b.fg, ColorProgressText), RealColor(b.fgActive, ColorProgressActiveText) bgOff, bgOn := RealColor(b.bg, ColorProgressBack), RealColor(b.bgActive, ColorProgressActiveBack) parts := []rune(SysObject(ObjProgressBar)) cFilled, cEmpty := parts[0], parts[1] prc := 0 if b.value >= b.max { prc = 100 } else if b.value < b.max && b.value > b.min { prc = (100 * (b.value - b.min)) / (b.max - b.min) } var title string if b.direction == Horizontal && b.Title() != "" { title = b.Title() title = strings.Replace(title, "{{percent}}", strconv.Itoa(prc), -1) title = strings.Replace(title, "{{value}}", strconv.Itoa(b.value), -1) title = strings.Replace(title, "{{min}}", strconv.Itoa(b.min), -1) title = strings.Replace(title, "{{max}}", strconv.Itoa(b.max), -1) } x, y := b.Pos() w, h := b.Size() if b.direction == Horizontal { filled := prc * w / 100 sFilled := strings.Repeat(string(cFilled), filled) sEmpty := strings.Repeat(string(cEmpty), w-filled) for yy := y; yy < y+h; yy++ { SetTextColor(fgOn) SetBackColor(bgOn) DrawRawText(x, yy, sFilled) SetTextColor(fgOff) SetBackColor(bgOff) DrawRawText(x+filled, yy, sEmpty) } if title != "" { shift, str := AlignText(title, w, b.align) titleClr := RealColor(b.titleFg, ColorProgressTitleText) var sOn, sOff string if filled == 0 || shift >= filled { sOff = str } else if w == filled || shift+xs.Len(str) < filled { sOn = str } else { r := filled - shift sOn = xs.Slice(str, 0, r) sOff = xs.Slice(str, r, -1) } SetTextColor(titleClr) if sOn != "" { SetBackColor(bgOn) DrawRawText(x+shift, y, sOn) } if sOff != "" { SetBackColor(bgOff) DrawRawText(x+shift+xs.Len(sOn), y, sOff) } } } else { filled := prc * h / 100 sFilled := strings.Repeat(string(cFilled), w) sEmpty := strings.Repeat(string(cEmpty), w) for yy := y; yy < y+h-filled; yy++ { SetTextColor(fgOff) SetBackColor(bgOff) DrawRawText(x, yy, sEmpty) } for yy := y + h - filled; yy < y+h; yy++ { SetTextColor(fgOff) SetBackColor(bgOff) DrawRawText(x, yy, sFilled) } } }
func (b *BarChart) drawBars() { if len(b.data) == 0 { return } start, width := b.calculateBarArea() if width < 2 { return } barW := b.calculateBarWidth() if barW == 0 { return } coeff, max := b.calculateMultiplier() if coeff == 0.0 { return } PushAttributes() defer PopAttributes() h := b.barHeight() pos := start parts := []rune(SysObject(ObjBarChart)) fg, bg := TextColor(), BackColor() for idx, d := range b.data { if pos+barW > start+width { break } fColor, bColor := d.Fg, d.Bg ch := d.Ch if fColor == ColorDefault { fColor = fg } if bColor == ColorDefault { bColor = bg } if ch == 0 { ch = parts[0] } barH := int(d.Value * coeff) if b.onDrawCell == nil { SetTextColor(fColor) SetBackColor(bColor) FillRect(b.x+pos, b.y+h-barH, barW, barH, ch) } else { cellDef := BarDataCell{Item: d.Title, ID: idx, Value: 0, BarMax: d.Value, TotalMax: max, Fg: fColor, Bg: bColor, Ch: ch} for dy := 0; dy < barH; dy++ { req := cellDef req.Value = max * float64(dy+1) / float64(h) b.onDrawCell(&req) SetTextColor(req.Fg) SetBackColor(req.Bg) for dx := 0; dx < barW; dx++ { PutChar(b.x+pos+dx, b.y+h-1-dy, req.Ch) } } } if b.showTitles { SetTextColor(fg) SetBackColor(bg) if b.showMarks { c := parts[7] PutChar(b.x+pos+barW/2, b.y+h, c) } var s string shift := 0 if xs.Len(d.Title) > barW { s = CutText(d.Title, barW) } else { shift, s = AlignText(d.Title, barW, AlignCenter) } DrawRawText(b.x+pos+shift, b.y+h+1, s) } pos += barW + b.gap } }