diff --git a/README.md b/README.md index 7a82dfb..46ac2cc 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ git clone https://github.com/eeeXun/gtt.git && cd gtt && go build -ldflags="-s - docker run -it eeexun/gtt:latest ``` -## Create a theme +## Customize theme You can create a theme with theme name. And you must provide the color of `bg`, `fg`, `gray`, `red`, `green`, `yellow`, `blue`, `purple`, `cyan`, `orange`. @@ -91,7 +91,7 @@ And note that: - `orange` is for KeyMap menu color - `purple` is for button pressed color -See the example in [theme.yaml](example/theme.yaml) file. This file should located at `$XDG_CONFIG_HOME/gtt/theme.yaml` or `$HOME/.config/gtt/theme.yaml` +See the example in [theme.yaml](example/theme.yaml) file. This file should be located at `$XDG_CONFIG_HOME/gtt/theme.yaml` or `$HOME/.config/gtt/theme.yaml`. ## Language in argument @@ -113,6 +113,8 @@ See available languages on: ## Key Map +### Default key map + `` Exit program. @@ -158,6 +160,31 @@ Cycle through the pop out widget. `<1>`, `<2>`, `<3>` Switch pop out window. +### Customize key map + +You can overwrite the following key + +- `exit`: Exit program. +- `translate`: Translate from source to destination window. +- `swap_language`: Swap language. +- `clear`: Clear all text in source of translation window. +- `copy_selected`: Copy selected text. +- `copy_source`: Copy all text in source of translation window. +- `copy_destination`: Copy all text in destination of translation window. +- `tts_source`: Play text to speech on source of translation window. +- `tts_destination`: Play text to speech on destination of translation window. +- `stop_tts`: Stop playing text to speech. +- `toggle_transparent`: Toggle transparent. +- `toggle_below`: Toggle Definition/Example & Part of speech. + +For key to combine with `Ctrl`, the value can be `"C-Space"`, `"C-\\"`, `"C-]"`, `"C-^"`, `"C-_"` or `"C-a"` to `"C-z"`. + +For key to combine with `Alt`, the value can be `"A-Space"` or `"A-"` + the character you want. + +Or you can use function key, the value can be `"F1"` to `"F64"`. + +See the example in [keymap.yaml](example/keymap.yaml) file. This file should be located at `$XDG_CONFIG_HOME/gtt/keymap.yaml` or `$HOME/.config/gtt/keymap.yaml`. + ## Credit [soimort/translate-shell](https://github.com/soimort/translate-shell), diff --git a/config.go b/config.go index 55fa6dd..2c7b990 100644 --- a/config.go +++ b/config.go @@ -14,7 +14,22 @@ func configInit() { var ( defaultConfigPath string themeConfig = config.New() - defaultConfig = map[string]interface{}{ + keyMapConfig = config.New() + defaultKeyMaps = map[string]string{ + "exit": "C-c", + "translate": "C-j", + "swap_language": "C-s", + "clear": "C-q", + "copy_selected": "C-y", + "copy_source": "C-g", + "copy_destination": "C-r", + "tts_source": "C-o", + "tts_destination": "C-p", + "stop_tts": "C-x", + "toggle_transparent": "C-t", + "toggle_below": "C-\\", + } + defaultConfig = map[string]interface{}{ "hide_below": false, "transparent": false, "theme": "gruvbox", @@ -42,17 +57,21 @@ func configInit() { config.SetConfigType("yaml") themeConfig.SetConfigName("theme") themeConfig.SetConfigType("yaml") + keyMapConfig.SetConfigName("keymap") + themeConfig.SetConfigType("yaml") if len(os.Getenv("XDG_CONFIG_HOME")) > 0 { defaultConfigPath = os.Getenv("XDG_CONFIG_HOME") + "/gtt" config.AddConfigPath(defaultConfigPath) themeConfig.AddConfigPath(defaultConfigPath) + keyMapConfig.AddConfigPath(defaultConfigPath) } else { defaultConfigPath = os.Getenv("HOME") + "/.config/gtt" } config.AddConfigPath("$HOME/.config/gtt") themeConfig.AddConfigPath("$HOME/.config/gtt") + keyMapConfig.AddConfigPath("$HOME/.config/gtt") - // import theme if file exists + // Import theme if file exists if err := themeConfig.ReadInConfig(); err == nil { var ( palate = make(map[string]int32) @@ -98,6 +117,22 @@ func configInit() { } } + // Setup key map + // If keymap file exist and action in file exist, then set the keyMap + // Otherwise, set to defaultKeyMap + if err := keyMapConfig.ReadInConfig(); err == nil { + for action, key := range defaultKeyMaps { + if keyMapConfig.Get(action) == nil { + keyMaps[action] = key + } else { + keyMaps[action] = keyMapConfig.GetString(action) + } + } + } else { + for action, key := range defaultKeyMaps { + keyMaps[action] = key + } + } // Setup for _, name := range translate.AllTranslator { translators[name] = translate.NewTranslator(name) diff --git a/example/keymap.yaml b/example/keymap.yaml new file mode 100644 index 0000000..5111f96 --- /dev/null +++ b/example/keymap.yaml @@ -0,0 +1,35 @@ +# This file should be located at $XDG_CONFIG_HOME/gtt/keymap.yaml or $HOME/.config/gtt/keymap.yaml. + +# For key to combine with Ctrl, the value can be "C-Space", "C-\\", "C-]", "C-^", "C-_" or "C-a" to "C-z". + +# For key to combine with Alt, the value can be "A-Space" or "A-" + the character you want. + +# Or you can use function key, the value can be "F1" to "F64". + + +# The following is the default key map + +# Exit program, +exit: "C-c" +# Translate from source to destination window, +translate: "C-j" +# Swap language, +swap_language: "C-s" +# Clear all text in source of translation window, +clear: "C-q" +# Copy selected text, +copy_selected: "C-y" +# Copy all text in source of translation window, +copy_source: "C-g" +# Copy all text in destination of translation window, +copy_destination: "C-r" +# Play text to speech on source of translation window, +tts_source: "C-o" +# Play text to speech on destination of translation window, +tts_destination: "C-p" +# Stop playing text to speech, +stop_tts: "C-x" +# Toggle transparent, +toggle_transparent: "C-t" +# Toggle Definition/Example & Part of speech, +toggle_below: "C-\\" diff --git a/example/theme.yaml b/example/theme.yaml index 9f08bde..8b10312 100644 --- a/example/theme.yaml +++ b/example/theme.yaml @@ -1,6 +1,6 @@ -# This file should located at $XDG_CONFIG_HOME/gtt/theme.yaml or $HOME/.config/gtt/theme.yaml -# You have to provide theme name. e.g. tokyonight -# And colors: bg, fg, gray, red, green, yellow, blue, purple, cyan, orange +# This file should be located at $XDG_CONFIG_HOME/gtt/theme.yaml or $HOME/.config/gtt/theme.yaml. +# You have to provide theme name. e.g. tokyonight. +# And colors: bg, fg, gray, red, green, yellow, blue, purple, cyan, orange. # bg is for background color # fg is for foreground color # gray is for selected color diff --git a/key.go b/key.go new file mode 100644 index 0000000..396dc1b --- /dev/null +++ b/key.go @@ -0,0 +1,121 @@ +package main + +import ( + "github.com/gdamore/tcell/v2" +) + +var keyNames = map[tcell.Key]string{ + tcell.KeyF1: "F1", + tcell.KeyF2: "F2", + tcell.KeyF3: "F3", + tcell.KeyF4: "F4", + tcell.KeyF5: "F5", + tcell.KeyF6: "F6", + tcell.KeyF7: "F7", + tcell.KeyF8: "F8", + tcell.KeyF9: "F9", + tcell.KeyF10: "F10", + tcell.KeyF11: "F11", + tcell.KeyF12: "F12", + tcell.KeyF13: "F13", + tcell.KeyF14: "F14", + tcell.KeyF15: "F15", + tcell.KeyF16: "F16", + tcell.KeyF17: "F17", + tcell.KeyF18: "F18", + tcell.KeyF19: "F19", + tcell.KeyF20: "F20", + tcell.KeyF21: "F21", + tcell.KeyF22: "F22", + tcell.KeyF23: "F23", + tcell.KeyF24: "F24", + tcell.KeyF25: "F25", + tcell.KeyF26: "F26", + tcell.KeyF27: "F27", + tcell.KeyF28: "F28", + tcell.KeyF29: "F29", + tcell.KeyF30: "F30", + tcell.KeyF31: "F31", + tcell.KeyF32: "F32", + tcell.KeyF33: "F33", + tcell.KeyF34: "F34", + tcell.KeyF35: "F35", + tcell.KeyF36: "F36", + tcell.KeyF37: "F37", + tcell.KeyF38: "F38", + tcell.KeyF39: "F39", + tcell.KeyF40: "F40", + tcell.KeyF41: "F41", + tcell.KeyF42: "F42", + tcell.KeyF43: "F43", + tcell.KeyF44: "F44", + tcell.KeyF45: "F45", + tcell.KeyF46: "F46", + tcell.KeyF47: "F47", + tcell.KeyF48: "F48", + tcell.KeyF49: "F49", + tcell.KeyF50: "F50", + tcell.KeyF51: "F51", + tcell.KeyF52: "F52", + tcell.KeyF53: "F53", + tcell.KeyF54: "F54", + tcell.KeyF55: "F55", + tcell.KeyF56: "F56", + tcell.KeyF57: "F57", + tcell.KeyF58: "F58", + tcell.KeyF59: "F59", + tcell.KeyF60: "F60", + tcell.KeyF61: "F61", + tcell.KeyF62: "F62", + tcell.KeyF63: "F63", + tcell.KeyF64: "F64", + tcell.KeyCtrlA: "C-a", + tcell.KeyCtrlB: "C-b", + tcell.KeyCtrlC: "C-c", + tcell.KeyCtrlD: "C-d", + tcell.KeyCtrlE: "C-e", + tcell.KeyCtrlF: "C-f", + tcell.KeyCtrlG: "C-g", + tcell.KeyCtrlJ: "C-j", + tcell.KeyCtrlK: "C-k", + tcell.KeyCtrlL: "C-l", + tcell.KeyCtrlN: "C-n", + tcell.KeyCtrlO: "C-o", + tcell.KeyCtrlP: "C-p", + tcell.KeyCtrlQ: "C-q", + tcell.KeyCtrlR: "C-r", + tcell.KeyCtrlS: "C-s", + tcell.KeyCtrlT: "C-t", + tcell.KeyCtrlU: "C-u", + tcell.KeyCtrlV: "C-v", + tcell.KeyCtrlW: "C-w", + tcell.KeyCtrlX: "C-x", + tcell.KeyCtrlY: "C-y", + tcell.KeyCtrlZ: "C-z", + tcell.KeyCtrlSpace: "C-Space", + tcell.KeyCtrlUnderscore: "C-_", + tcell.KeyCtrlRightSq: "C-]", + tcell.KeyCtrlBackslash: "C-\\", + tcell.KeyCtrlCarat: "C-^", +} + +func getKeyName(event *tcell.EventKey) string { + var ( + keyName string + key = event.Key() + ) + + if key == tcell.KeyRune { + if event.Modifiers() == tcell.ModAlt { + if event.Rune() == ' ' { + keyName = "A-Space" + } else { + keyName = "A-" + string(event.Rune()) + } + } + } else { + keyName = keyNames[key] + } + + return keyName +} diff --git a/main.go b/main.go index be61aa9..06d96d0 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,8 @@ var ( translators = make(map[string]translate.Translator, len(translate.AllTranslator)) // UI style uiStyle = style.NewStyle() + // keyMaps + keyMaps = make(map[string]string) // UI app = tview.NewApplication() srcInput = tview.NewTextArea() diff --git a/ui.go b/ui.go index 34d6d26..bc80828 100644 --- a/ui.go +++ b/ui.go @@ -10,6 +10,13 @@ import ( "github.com/rivo/tview" ) +type Item struct { + item tview.Primitive + fixedSize int + proportion int + focus bool +} + const ( popOutWindowHeight int = 20 langStrMaxLength int = 32 @@ -17,27 +24,27 @@ const ( Exit program. [#%[1]s][-] Toggle pop out window. -[#%[1]s][-] +[#%[1]s]<%[2]s>[-] Translate from source to destination window. -[#%[1]s][-] +[#%[1]s]<%[3]s>[-] Swap language. -[#%[1]s][-] +[#%[1]s]<%[4]s>[-] Clear all text in source of translation window. -[#%[1]s][-] +[#%[1]s]<%[5]s>[-] Copy selected text. -[#%[1]s][-] +[#%[1]s]<%[6]s>[-] Copy all text in source of translation window. -[#%[1]s][-] +[#%[1]s]<%[7]s>[-] Copy all text in destination of translation window. -[#%[1]s][-] +[#%[1]s]<%[8]s>[-] Play text to speech on source of translation window. -[#%[1]s][-] +[#%[1]s]<%[9]s>[-] Play text to speech on destination of translation window. -[#%[1]s][-] +[#%[1]s]<%[10]s>[-] Stop playing text to speech. -[#%[1]s][-] +[#%[1]s]<%[11]s>[-] Toggle transparent. -[#%[1]s][-] +[#%[1]s]<%[12]s>[-] Toggle Definition/Example & Part of speech. [#%[1]s], [-] Cycle through the pop out widget. @@ -45,13 +52,6 @@ const ( Switch pop out window.` ) -type Item struct { - item tview.Primitive - fixedSize int - proportion int - focus bool -} - func updateTranslateWindow() { if uiStyle.HideBelow { translateWindow.RemoveItem(translateBelowWidget) @@ -167,8 +167,19 @@ func updateNonConfigColor() { // key map keyMapMenu.SetTextColor(uiStyle.ForegroundColor()). SetText(fmt.Sprintf(keyMapText, - fmt.Sprintf("%.6x", - uiStyle.HighLightColor().TrueColor().Hex()))). + fmt.Sprintf("%.6x", uiStyle.HighLightColor().TrueColor().Hex()), + keyMaps["translate"], + keyMaps["swap_language"], + keyMaps["clear"], + keyMaps["copy_selected"], + keyMaps["copy_source"], + keyMaps["copy_destination"], + keyMaps["tts_source"], + keyMaps["tts_destination"], + keyMaps["stop_tts"], + keyMaps["toggle_transparent"], + keyMaps["toggle_below"], + )). SetBorderColor(uiStyle.HighLightColor()). SetTitleColor(uiStyle.HighLightColor()) } @@ -358,16 +369,21 @@ func uiInit() { updateCurrentLang() // handler - mainPage.SetInputCapture(mainPageHandler) + app.SetInputCapture(appHandler) translateWindow.SetInputCapture(translateWindowHandler) for _, widget := range []*tview.TextArea{srcInput, defOutput, posOutput} { // fix for loop problem // https://github.com/golang/go/discussions/56010 widget := widget widget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { - key := event.Key() - switch key { - case tcell.KeyCtrlY: + keyName := getKeyName(event) + + if len(keyName) == 0 { + return event + } + + switch keyName { + case keyMaps["copy_selected"]: // copy selected text text, _, _ := widget.GetSelection() @@ -375,6 +391,7 @@ func uiInit() { if len(text) > 0 { CopyToClipboard(text) } + return nil } return event }) @@ -427,37 +444,54 @@ func uiInit() { keyMapButton.SetSelectedFunc(showKeyMapPopout) } -func mainPageHandler(event *tcell.EventKey) *tcell.EventKey { - key := event.Key() +func appHandler(event *tcell.EventKey) *tcell.EventKey { + keyName := getKeyName(event) - switch key { - case tcell.KeyCtrlT: + if len(keyName) == 0 { + return event + } + + switch keyName { + case keyMaps["exit"]: + app.Stop() + return nil + case keyMaps["toggle_transparent"]: // Toggle transparent uiStyle.Transparent = !uiStyle.Transparent // The following will trigger transparentDropDown SetDoneFunc transparentDropDown.SetCurrentOption( IndexOf(strconv.FormatBool(uiStyle.Transparent), []string{"true", "false"})) - case tcell.KeyCtrlBackslash: + return nil + case keyMaps["toggle_below"]: // Toggle Hide below window uiStyle.HideBelow = !uiStyle.HideBelow // The following will trigger hideBelowDropDown SetDoneFunc hideBelowDropDown.SetCurrentOption( IndexOf(strconv.FormatBool(uiStyle.HideBelow), []string{"true", "false"})) + return nil + } + + // Force C-c not to exit program + if event.Key() == tcell.KeyCtrlC { + return tcell.NewEventKey(tcell.KeyCtrlC, 0, tcell.ModNone) } return event } func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { - key := event.Key() - - switch key { - case tcell.KeyEsc: + if event.Key() == tcell.KeyEsc { mainPage.ShowPage("langPopOut") app.SetFocus(langCycle.GetCurrentUI()) - case tcell.KeyCtrlJ: + return nil + } + + keyName := getKeyName(event) + + switch keyName { + case keyMaps["translate"]: message := srcInput.GetText() // Only translate when message exist if len(message) > 0 { @@ -470,9 +504,11 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { posOutput.SetText(translation.POS, false) } } - case tcell.KeyCtrlQ: + return nil + case keyMaps["clear"]: srcInput.SetText("", true) - case tcell.KeyCtrlG: + return nil + case keyMaps["copy_source"]: // copy all text in Input text := srcInput.GetText() @@ -480,7 +516,8 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { if len(text) > 0 { CopyToClipboard(text) } - case tcell.KeyCtrlR: + return nil + case keyMaps["copy_destination"]: // copy all text in Output text := dstOutput.GetText(false) @@ -488,7 +525,8 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { if len(text) > 0 { CopyToClipboard(text[:len(text)-1]) } - case tcell.KeyCtrlS: + return nil + case keyMaps["swap_language"]: translator.SwapLang() updateCurrentLang() srcText := srcInput.GetText() @@ -500,7 +538,8 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { srcInput.SetText(dstText, true) } dstOutput.SetText(srcText) - case tcell.KeyCtrlO: + return nil + case keyMaps["tts_source"]: // Play text to speech on source of translation window. if translator.LockAvailable() { message := srcInput.GetText() @@ -517,7 +556,8 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { } } - case tcell.KeyCtrlP: + return nil + case keyMaps["tts_destination"]: // Play text to speech on destination of translation window. if translator.LockAvailable() { message := dstOutput.GetText(false) @@ -533,9 +573,11 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { }() } } - case tcell.KeyCtrlX: + return nil + case keyMaps["stop_tts"]: // Stop play sound translator.StopTTS() + return nil } return event