2015-09-21 20:54:39 -07:00
|
|
|
package clui
|
|
|
|
|
2015-10-16 10:27:43 -07:00
|
|
|
import (
|
2015-10-20 14:32:16 -07:00
|
|
|
"bufio"
|
2015-10-16 10:27:43 -07:00
|
|
|
term "github.com/nsf/termbox-go"
|
2015-10-20 14:32:16 -07:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"strings"
|
2015-10-16 10:27:43 -07:00
|
|
|
)
|
|
|
|
|
2015-09-21 20:54:39 -07:00
|
|
|
/*
|
|
|
|
Theme support for controls.
|
|
|
|
The current implementation is limited but later the manager will be
|
|
|
|
able to load a requested theme on demand and use deep inheritance.
|
|
|
|
Theme 'default' exists always - it is predefinded and always complete.
|
|
|
|
User-defined themes may omit any theme section, all omitted items
|
|
|
|
are loaded from parent theme. The only required property that a user-
|
|
|
|
defined theme must have is a theme name.
|
|
|
|
*/
|
|
|
|
type ThemeManager struct {
|
|
|
|
// available theme list
|
|
|
|
themes map[string]theme
|
|
|
|
// name of the current theme
|
2015-10-20 14:32:16 -07:00
|
|
|
current string
|
|
|
|
themePath string
|
|
|
|
version string
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
|
2015-10-20 15:46:14 -07:00
|
|
|
type ThemeInfo struct {
|
|
|
|
parent string
|
|
|
|
title string
|
|
|
|
author string
|
|
|
|
version string
|
|
|
|
}
|
|
|
|
|
2015-09-21 20:54:39 -07:00
|
|
|
/*
|
|
|
|
A theme structure. It keeps all colors, characters for the theme.
|
|
|
|
Parent property determines a theme name that is used if a requested
|
|
|
|
theme object is not declared in the current one. If no parent is
|
|
|
|
defined then the library uses default built-in theme.
|
|
|
|
*/
|
|
|
|
type theme struct {
|
|
|
|
parent string
|
2015-10-20 14:32:16 -07:00
|
|
|
title string
|
|
|
|
author string
|
|
|
|
version string
|
2015-10-20 15:46:14 -07:00
|
|
|
colors map[string]term.Attribute
|
|
|
|
objects map[string]string
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const defaultTheme = "default"
|
|
|
|
|
|
|
|
func NewThemeManager() *ThemeManager {
|
|
|
|
sm := new(ThemeManager)
|
|
|
|
|
2015-10-20 14:32:16 -07:00
|
|
|
sm.Reset()
|
|
|
|
|
|
|
|
return sm
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ThemeManager) Reset() {
|
|
|
|
s.current = defaultTheme
|
|
|
|
s.themes = make(map[string]theme, 0)
|
|
|
|
|
|
|
|
defTheme := theme{parent: "", title: "Default Theme", author: "V. Markelov", version: "1.0"}
|
|
|
|
defTheme.colors = make(map[string]term.Attribute, 0)
|
|
|
|
defTheme.objects = make(map[string]string, 0)
|
2015-10-16 10:27:43 -07:00
|
|
|
|
|
|
|
defTheme.objects[ObjSingleBorder] = "─│┌┐└┘"
|
|
|
|
defTheme.objects[ObjDoubleBorder] = "═║╔╗╚╝"
|
|
|
|
defTheme.objects[ObjEdit] = "←→V"
|
|
|
|
defTheme.objects[ObjScrollBar] = "|O^v"
|
|
|
|
defTheme.objects[ObjViewButtons] = "^↓○[]"
|
|
|
|
defTheme.objects[ObjCheckBox] = "[] X?"
|
|
|
|
defTheme.objects[ObjRadio] = "() *"
|
|
|
|
defTheme.objects[ObjProgressBar] = "░▒"
|
|
|
|
|
2015-10-19 12:05:43 -07:00
|
|
|
defTheme.colors[ColorDisabledText] = ColorBlackBold
|
|
|
|
defTheme.colors[ColorDisabledBack] = ColorWhite
|
2015-10-16 10:27:43 -07:00
|
|
|
defTheme.colors[ColorText] = ColorWhite
|
2015-10-19 16:54:26 -07:00
|
|
|
defTheme.colors[ColorBack] = ColorBlackBold
|
2015-10-16 10:27:43 -07:00
|
|
|
defTheme.colors[ColorViewBack] = ColorBlackBold
|
|
|
|
defTheme.colors[ColorViewText] = ColorWhite
|
2015-09-21 20:54:39 -07:00
|
|
|
|
2015-10-19 12:05:43 -07:00
|
|
|
defTheme.colors[ColorControlText] = ColorWhite
|
|
|
|
defTheme.colors[ColorControlBack] = ColorBlack
|
|
|
|
defTheme.colors[ColorControlActiveText] = ColorWhite
|
|
|
|
defTheme.colors[ColorControlActiveBack] = ColorMagenta
|
|
|
|
defTheme.colors[ColorControlShadow] = ColorBlue
|
|
|
|
defTheme.colors[ColorControlDisabledText] = ColorWhite
|
|
|
|
defTheme.colors[ColorControlDisabledBack] = ColorBlackBold
|
|
|
|
|
|
|
|
defTheme.colors[ColorEditText] = ColorBlack
|
|
|
|
defTheme.colors[ColorEditBack] = ColorWhite
|
|
|
|
defTheme.colors[ColorEditActiveText] = ColorBlack
|
|
|
|
defTheme.colors[ColorEditActiveBack] = ColorWhiteBold
|
|
|
|
defTheme.colors[ColorSelectionText] = ColorYellow
|
|
|
|
defTheme.colors[ColorSelectionBack] = ColorBlue
|
|
|
|
|
|
|
|
defTheme.colors[ColorScrollBack] = ColorBlackBold
|
|
|
|
defTheme.colors[ColorScrollText] = ColorWhite
|
|
|
|
defTheme.colors[ColorThumbBack] = ColorBlackBold
|
|
|
|
defTheme.colors[ColorThumbText] = ColorWhite
|
|
|
|
|
|
|
|
defTheme.colors[ColorProgressText] = ColorBlue
|
|
|
|
defTheme.colors[ColorProgressBack] = ColorBlackBold
|
|
|
|
defTheme.colors[ColorProgressActiveText] = ColorBlack
|
|
|
|
defTheme.colors[ColorProgressActiveBack] = ColorBlueBold
|
|
|
|
|
2015-10-20 14:32:16 -07:00
|
|
|
s.themes[defaultTheme] = defTheme
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
|
2015-10-20 14:32:16 -07:00
|
|
|
func (s *ThemeManager) SysColor(color string) term.Attribute {
|
2015-09-21 20:54:39 -07:00
|
|
|
sch, ok := s.themes[s.current]
|
|
|
|
if !ok {
|
|
|
|
sch = s.themes[defaultTheme]
|
|
|
|
}
|
|
|
|
|
|
|
|
clr, okclr := sch.colors[color]
|
2015-10-21 10:22:31 -07:00
|
|
|
if !okclr {
|
|
|
|
visited := make(map[string]int, 0)
|
|
|
|
visited[s.current] = 1
|
|
|
|
if !ok {
|
|
|
|
visited[defaultTheme] = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
s.LoadTheme(sch.parent)
|
|
|
|
sch = s.themes[sch.parent]
|
|
|
|
clr, okclr = sch.colors[color]
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
if _, okSch := visited[sch.parent]; okSch {
|
|
|
|
panic("Color + " + color + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
|
|
|
|
} else {
|
|
|
|
visited[sch.parent] = 1
|
|
|
|
}
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return clr
|
|
|
|
}
|
|
|
|
|
2015-10-20 14:32:16 -07:00
|
|
|
func (s *ThemeManager) SysObject(object string) string {
|
2015-09-21 20:54:39 -07:00
|
|
|
sch, ok := s.themes[s.current]
|
|
|
|
if !ok {
|
|
|
|
sch = s.themes[defaultTheme]
|
|
|
|
}
|
|
|
|
|
|
|
|
obj, okobj := sch.objects[object]
|
2015-10-21 10:22:31 -07:00
|
|
|
if !okobj {
|
|
|
|
visited := make(map[string]int, 0)
|
|
|
|
visited[s.current] = 1
|
|
|
|
if !ok {
|
|
|
|
visited[defaultTheme] = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
s.LoadTheme(sch.parent)
|
|
|
|
sch = s.themes[sch.parent]
|
|
|
|
obj, okobj = sch.objects[object]
|
|
|
|
|
|
|
|
if ok {
|
|
|
|
break
|
|
|
|
} else {
|
|
|
|
if _, okSch := visited[sch.parent]; okSch {
|
|
|
|
panic("Object: " + object + ". Theme loop detected: " + sch.title + " --> " + sch.parent)
|
|
|
|
} else {
|
|
|
|
visited[sch.parent] = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return obj
|
|
|
|
}
|
|
|
|
|
2015-10-20 15:46:14 -07:00
|
|
|
func (s *ThemeManager) ThemeNames() []string {
|
2015-10-20 14:32:16 -07:00
|
|
|
var str []string
|
|
|
|
str = append(str, defaultTheme)
|
|
|
|
|
|
|
|
path := s.themePath
|
|
|
|
if path == "" {
|
|
|
|
path = "." + string(os.PathSeparator)
|
|
|
|
}
|
|
|
|
files, err := ioutil.ReadDir(path)
|
|
|
|
if err != nil {
|
|
|
|
panic("Failed to read theme directory: " + s.themePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range files {
|
|
|
|
if !f.IsDir() {
|
|
|
|
str = append(str, f.Name())
|
|
|
|
}
|
2015-09-21 20:54:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
|
2015-10-19 17:23:31 -07:00
|
|
|
func (s *ThemeManager) CurrentTheme() string {
|
2015-09-21 20:54:39 -07:00
|
|
|
return s.current
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ThemeManager) SetCurrentTheme(name string) bool {
|
2015-10-20 14:32:16 -07:00
|
|
|
if _, ok := s.themes[name]; !ok {
|
2015-10-20 15:46:14 -07:00
|
|
|
tnames := s.ThemeNames()
|
2015-10-20 14:32:16 -07:00
|
|
|
for _, theme := range tnames {
|
|
|
|
if theme == name {
|
|
|
|
s.LoadTheme(theme)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-21 20:54:39 -07:00
|
|
|
if _, ok := s.themes[name]; ok {
|
|
|
|
s.current = name
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2015-10-20 14:32:16 -07:00
|
|
|
|
|
|
|
func (s *ThemeManager) ThemePath() string {
|
|
|
|
return s.themePath
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ThemeManager) SetThemePath(path string) {
|
|
|
|
if path == s.themePath {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
s.themePath = path
|
|
|
|
s.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ThemeManager) LoadTheme(name string) {
|
|
|
|
if _, ok := s.themes[name]; ok {
|
2015-10-21 10:22:31 -07:00
|
|
|
return
|
2015-10-20 14:32:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
theme := theme{parent: "", title: "", author: ""}
|
|
|
|
theme.colors = make(map[string]term.Attribute, 0)
|
|
|
|
theme.objects = make(map[string]string, 0)
|
|
|
|
|
|
|
|
file, err := os.Open(s.themePath + string(os.PathSeparator) + name)
|
|
|
|
if err != nil {
|
|
|
|
panic("Failed to open theme " + name + " : " + err.Error())
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
for scanner.Scan() {
|
|
|
|
line := scanner.Text()
|
|
|
|
line = strings.Trim(line, " ")
|
|
|
|
|
|
|
|
// skip comments
|
|
|
|
if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "/") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip invalid lines
|
|
|
|
if !strings.Contains(line, "=") {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
parts := strings.SplitN(line, "=", 2)
|
|
|
|
key := strings.Trim(parts[0], " ")
|
|
|
|
value := strings.Trim(parts[1], " ")
|
|
|
|
|
|
|
|
low := strings.ToLower(key)
|
|
|
|
if low == "parent" {
|
|
|
|
theme.parent = value
|
|
|
|
} else if low == "author" {
|
|
|
|
theme.author = value
|
|
|
|
} else if low == "name" || low == "title" {
|
|
|
|
theme.title = value
|
|
|
|
} else if low == "version" {
|
|
|
|
theme.version = value
|
|
|
|
} else if strings.HasSuffix(key, "Back") || strings.HasSuffix(key, "Text") {
|
|
|
|
c := StringToColor(value)
|
|
|
|
if c%32 == 0 {
|
|
|
|
panic("Failed to read color: " + value)
|
|
|
|
}
|
|
|
|
theme.colors[key] = c
|
|
|
|
} else {
|
|
|
|
theme.objects[key] = value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if theme.parent == "" {
|
|
|
|
theme.parent = "default"
|
|
|
|
}
|
|
|
|
|
|
|
|
s.themes[name] = theme
|
|
|
|
}
|
2015-10-20 15:46:14 -07:00
|
|
|
|
2015-10-21 10:22:31 -07:00
|
|
|
func (s *ThemeManager) ReLoadTheme(name string) {
|
|
|
|
if _, ok := s.themes[name]; ok {
|
|
|
|
delete(s.themes, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
s.LoadTheme(name)
|
|
|
|
}
|
|
|
|
|
2015-10-20 15:46:14 -07:00
|
|
|
func (s *ThemeManager) ThemeInfo(name string) ThemeInfo {
|
|
|
|
s.LoadTheme(name)
|
|
|
|
var theme ThemeInfo
|
|
|
|
if t, ok := s.themes[name]; !ok {
|
|
|
|
theme.parent = t.parent
|
|
|
|
theme.title = t.title
|
|
|
|
theme.version = t.version
|
|
|
|
}
|
|
|
|
return theme
|
|
|
|
}
|