Skip to content

Commit

Permalink
Logging.
Browse files Browse the repository at this point in the history
Added menus.
Can now send raw stanzas.
  • Loading branch information
remicorniere committed Dec 18, 2019
1 parent 1ba2add commit c006990
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 77 deletions.
3 changes: 3 additions & 0 deletions _examples/xmpp_chat_client/config.yml
Expand Up @@ -9,4 +9,7 @@ Client :

Contacts : "testuser1@localhost;testuser3@localhost"

LogStanzas:
- logger_on: "true"
- logfile_path: "./logs"

223 changes: 192 additions & 31 deletions _examples/xmpp_chat_client/interface.go
@@ -1,15 +1,48 @@
package main

import (
"errors"
"fmt"
"github.com/awesome-gocui/gocui"
"log"
"strings"
)

const (
chatLogWindow = "clw"
inputWindow = "iw"
menuWindow = "menw"
// Windows
chatLogWindow = "clw" // Where (received and sent) messages are logged
chatInputWindow = "iw" // Where messages are written
rawInputWindow = "rw" // Where raw stanzas are written
contactsListWindow = "cl" // Where the contacts list is shown, and contacts are selectable
menuWindow = "mw" // Where the menu is shown

// Menu options
disconnect = "Disconnect"
askServerForRoster = "Ask server for roster"
rawMode = "Switch to Send Raw Mode"
messageMode = "Switch to Send Message Mode"
contactList = "Contacts list"
backFromContacts = "<- Go back"
)

// To store names of views on top
type viewsState struct {
input string // Which input view is on top
side string // Which side view is on top
contacts []string // Contacts list
currentContact string // Contact we are currently messaging
}

var (
// Which window is on top currently on top of the other.
// This is the init setup
viewState = viewsState{
input: chatInputWindow,
side: menuWindow,
}
menuOptions = []string{contactList, rawMode, askServerForRoster, disconnect}
// Errors
servConnFail = errors.New("failed to connect to server. Check your configuration ? Exiting")
)

func setCurrentViewOnTop(g *gocui.Gui, name string) (*gocui.View, error) {
Expand All @@ -31,7 +64,7 @@ func layout(g *gocui.Gui) error {
v.Autoscroll = true
}

if v, err := g.SetView(menuWindow, 0, 0, maxX/5-1, 5*maxY/6-1, 0); err != nil {
if v, err := g.SetView(contactsListWindow, 0, 0, maxX/5-1, 5*maxY/6-2, 0); err != nil {
if !gocui.IsUnknownView(err) {
return err
}
Expand All @@ -40,15 +73,37 @@ func layout(g *gocui.Gui) error {
v.Autoscroll = true
}

if v, err := g.SetView(inputWindow, 0, 5*maxY/6-1, maxX/1-1, maxY-1, 0); err != nil {
if v, err := g.SetView(menuWindow, 0, 0, maxX/5-1, 5*maxY/6-2, 0); err != nil {
if !gocui.IsUnknownView(err) {
return err
}
v.Title = "Menu"
v.Wrap = true
v.Autoscroll = true
fmt.Fprint(v, strings.Join(menuOptions, "\n"))
if _, err = setCurrentViewOnTop(g, menuWindow); err != nil {
return err
}
}

if v, err := g.SetView(rawInputWindow, 0, 5*maxY/6-1, maxX/1-1, maxY-1, 0); err != nil {
if !gocui.IsUnknownView(err) {
return err
}
v.Title = "Write or paste a raw stanza. Press \"Ctrl+E\" to send :"
v.Editable = true
v.Wrap = true
}

if v, err := g.SetView(chatInputWindow, 0, 5*maxY/6-1, maxX/1-1, maxY-1, 0); err != nil {
if !gocui.IsUnknownView(err) {
return err
}
v.Title = "Write a message :"
v.Editable = true
v.Wrap = true

if _, err = setCurrentViewOnTop(g, inputWindow); err != nil {
if _, err = setCurrentViewOnTop(g, chatInputWindow); err != nil {
return err
}
}
Expand All @@ -60,50 +115,83 @@ func quit(g *gocui.Gui, v *gocui.View) error {
return gocui.ErrQuit
}

// Sends an input line from the user to the backend while also printing it in the chatlog window.
// Sends an input text from the user to the backend while also printing it in the chatlog window.
// KeyEnter is viewed as "\n" by gocui, so messages should only be one line, whereas raw sending has a different key
// binding and therefor should work with this too (for multiple lines stanzas)
func writeInput(g *gocui.Gui, v *gocui.View) error {
log, _ := g.View(chatLogWindow)
for _, line := range v.ViewBufferLines() {
textChan <- line
fmt.Fprintln(log, "Me : ", line)
}
chatLogWindow, _ := g.View(chatLogWindow)

input := strings.Join(v.ViewBufferLines(), "\n")

fmt.Fprintln(chatLogWindow, "Me : ", input)
textChan <- input

v.Clear()
v.EditDeleteToStartOfLine()
return nil
}

func setKeyBindings(g *gocui.Gui) {
// ==========================
// All views
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
log.Panicln(err)
}

if err := g.SetKeybinding(inputWindow, gocui.KeyEnter, gocui.ModNone, writeInput); err != nil {
// ==========================
// Chat input
if err := g.SetKeybinding(chatInputWindow, gocui.KeyEnter, gocui.ModNone, writeInput); err != nil {
log.Panicln(err)
}

if err := g.SetKeybinding(inputWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
if err := g.SetKeybinding(chatInputWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
log.Panicln(err)
}

// ==========================
// Raw input
if err := g.SetKeybinding(rawInputWindow, gocui.KeyCtrlE, gocui.ModNone, writeInput); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding(menuWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {

if err := g.SetKeybinding(rawInputWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
log.Panicln(err)
}

// ==========================
// Menu
if err := g.SetKeybinding(menuWindow, gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
log.Panicln(err)

}
if err := g.SetKeybinding(menuWindow, gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
log.Panicln(err)

}
if err := g.SetKeybinding(menuWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding(menuWindow, gocui.KeyEnter, gocui.ModNone, getLine); err != nil {
log.Panicln(err)
}

// ==========================
// Contacts list
if err := g.SetKeybinding(contactsListWindow, gocui.KeyArrowDown, gocui.ModNone, cursorDown); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding(contactsListWindow, gocui.KeyArrowUp, gocui.ModNone, cursorUp); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding(contactsListWindow, gocui.KeyEnter, gocui.ModNone, getLine); err != nil {
log.Panicln(err)
}
if err := g.SetKeybinding(contactsListWindow, gocui.KeyCtrlSpace, gocui.ModNone, nextView); err != nil {
log.Panicln(err)
}

}

// When we select a new correspondent, we change it in the client, and we display a message window confirming the change.
// General
// Used to handle menu selections and navigations
func getLine(g *gocui.Gui, v *gocui.View) error {
var l string
var err error
Expand All @@ -112,34 +200,107 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
if l, err = v.Line(cy); err != nil {
l = ""
}
// Updating the current correspondent, back-end side.
CorrespChan <- l
if viewState.side == menuWindow {
if l == contactList {
cv, _ := g.View(contactsListWindow)
viewState.side = contactsListWindow
g.SetViewOnTop(contactsListWindow)
g.SetCurrentView(contactsListWindow)
if len(cv.ViewBufferLines()) == 0 {
printContactsToWindow(g, viewState.contacts)
}
} else if l == disconnect || l == askServerForRoster {
chlw, _ := g.View(chatLogWindow)
fmt.Fprintln(chlw, infoFormat+" Not yet implemented !")
} else if l == rawMode {
mw, _ := g.View(menuWindow)
viewState.input = rawInputWindow
g.SetViewOnTop(rawInputWindow)
g.SetCurrentView(rawInputWindow)
menuOptions[1] = messageMode
v.Clear()
v.EditDeleteToStartOfLine()
fmt.Fprintln(mw, strings.Join(menuOptions, "\n"))
message := "Now sending in raw stanza mode"
clv, _ := g.View(chatLogWindow)
fmt.Fprintln(clv, infoFormat+message)
} else if l == messageMode {
mw, _ := g.View(menuWindow)
viewState.input = chatInputWindow
g.SetViewOnTop(chatInputWindow)
g.SetCurrentView(chatInputWindow)
menuOptions[1] = rawMode
v.Clear()
v.EditDeleteToStartOfLine()
fmt.Fprintln(mw, strings.Join(menuOptions, "\n"))
message := "Now sending in messages mode"
clv, _ := g.View(chatLogWindow)
fmt.Fprintln(clv, infoFormat+message)
}
} else if viewState.side == contactsListWindow {
if l == backFromContacts {
viewState.side = menuWindow
g.SetViewOnTop(menuWindow)
g.SetCurrentView(menuWindow)
} else if l == "" {
return nil
} else {
// Updating the current correspondent, back-end side.
CorrespChan <- l
viewState.currentContact = l
// Showing the selected contact in contacts list
cl, _ := g.View(contactsListWindow)
cts := cl.ViewBufferLines()
cl.Clear()
printContactsToWindow(g, cts)
// Showing a message to the user, and switching back to input after the new contact is selected.
message := "Now sending messages to : " + l + " in a private conversation"
clv, _ := g.View(chatLogWindow)
fmt.Fprintln(clv, infoFormat+message)
g.SetCurrentView(chatInputWindow)
}
}

// Showing a message to the user, and switching back to input after the new contact is selected.
message := "Now sending messages to : " + l + " in a private conversation"
clv, _ := g.View(chatLogWindow)
fmt.Fprintln(clv, infoFormat+message)
g.SetCurrentView(inputWindow)
return nil
}

// Changing view between input and "menu" (= basically contacts only right now) when pressing the specific key.
func printContactsToWindow(g *gocui.Gui, contactsList []string) {
cl, _ := g.View(contactsListWindow)
for _, c := range contactsList {
c = strings.ReplaceAll(c, " *", "")
if c == viewState.currentContact {
fmt.Fprintf(cl, c+" *\n")
} else {
fmt.Fprintf(cl, c+"\n")
}
}
}

// Changing view between input and "menu/contacts" when pressing the specific key.
func nextView(g *gocui.Gui, v *gocui.View) error {
if v == nil || v.Name() == inputWindow {
_, err := g.SetCurrentView(menuWindow)
if v == nil || v.Name() == chatInputWindow || v.Name() == rawInputWindow {
_, err := g.SetCurrentView(viewState.side)
return err
} else if v.Name() == menuWindow || v.Name() == contactsListWindow {
_, err := g.SetCurrentView(viewState.input)
return err
}
_, err := g.SetCurrentView(inputWindow)

// Should not be reached right now
_, err := g.SetCurrentView(chatInputWindow)
return err
}

func cursorDown(g *gocui.Gui, v *gocui.View) error {
if v != nil {
cx, cy := v.Cursor()
// Avoid going below the list of contacts
// Avoid going below the list of contacts. Although lines are stored in the view as a slice
// in the used lib. Therefor, if the number of lines is too big, the cursor will go past the last line since
// increasing slice capacity is done by doubling it. Last lines will be "nil" and reachable by the cursor
// in a dynamic context (such as contacts list)
cv := g.CurrentView()
h := cv.LinesHeight()
if cy+1 >= h-1 {
if cy+1 >= h {
return nil
}
// Lower cursor
Expand Down

0 comments on commit c006990

Please sign in to comment.