@@ -1,17 +1,17 @@
package main
import (
"encoding/json"
//core - required
"encoding/xml"
"fmt"
"image/color"
"io"
_ "io/fs"
"log"
"net/url"
"os"
"strings"
"time"
// gui - required
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
@@ -20,13 +20,23 @@ import (
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
catppuccin "github.com/mbaklor/fyne-catppuccin"
// xmpp - required
"mellium.im/xmpp/disco"
"mellium.im/xmpp/jid"
"mellium.im/xmpp/muc"
oasisSdk "pain.agency/oasis-sdk"
// gui - optional
// catppuccin "github.com/mbaklor/fyne-catppuccin"
adwaita "fyne.io/x/fyne/theme"
// TODO: integrated theme switcher
)
var version string = "3.1a"
var statBar widget . Label
var chatInfo fyne . Container
var chatSidebar fyne . Container
// by sunglocto
// license AGPL
@@ -46,6 +56,7 @@ type MucTab struct {
Messages [ ] Message
Scroller * widget . List
isMuc bool
Muc * muc . Channel
}
type piConfig struct {
@@ -68,7 +79,7 @@ var connection bool = true
type myTheme struct { }
func ( m myTheme ) Color ( name fyne . ThemeColorName , variant fyne . ThemeVariant ) color . Color {
return catppuccin . New ( ) . Color ( name , variant )
return adwaita . AdwaitaTheme ( ) . Color ( name , variant )
}
func ( m myTheme ) Icon ( name fyne . ThemeIconName ) fyne . Resource {
@@ -114,8 +125,8 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
author . TextStyle . Bold = true
content := widget . NewRichTextWithText ( "content" )
content . Wrapping = fyne . TextWrapWord
icon := theme . FileImageIcon ( )
btn := widget . NewButtonWithIcon ( "View image " , icon , func ( ) {
icon := theme . FileVideoIcon ( )
btn := widget . NewButtonWithIcon ( "View media " , icon , func ( ) {
} )
return container . NewVBox ( author , content , btn )
@@ -131,7 +142,20 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
btn . Hidden = false
btn . OnTapped = func ( ) {
fyne . Do ( func ( ) {
u , _ := storage . ParseURI ( tabData . Messages [ i ] . ImageURL )
u , err := storage . ParseURI ( tabData . Messages [ i ] . ImageURL )
if err != nil {
dialog . ShowError ( err , w )
return
}
if strings . HasSuffix ( tabData . Messages [ i ] . ImageURL , "mp4" ) {
url , err := url . Parse ( tabData . Messages [ i ] . ImageURL )
if err != nil {
dialog . ShowError ( err , w )
return
}
a . OpenURL ( url )
return
}
image := canvas . NewImageFromURI ( u )
image . FillMode = canvas . ImageFillOriginal
dialog . ShowCustom ( "Image" , "Close" , image , w )
@@ -142,14 +166,14 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
lines := strings . Split ( msgContent , "\n" )
for i , line := range lines {
if strings . HasPrefix ( line , ">" ) {
lines [ i ] = "\n" + line + "\n"
lines [ i ] = fmt . Sprintf ( "\n %s \n" , line )
}
}
msgContent = strings . Join ( lines , "\n" )
content . ParseMarkdown ( msgContent )
if tabData . Messages [ i ] . ReplyID != "PICLIENT:UNAVAILABLE" {
author . SetText ( fmt . Sprintf ( "%s ↳ %s" , tabData . Messages [ i ] . Author , jid . MustParse ( tabData . Messages [ i ] . ReplyID ) . Resourcepart ( ) ) )
author . SetText ( fmt . Sprintf ( "%s > %s" , tabData . Messages [ i ] . Author , jid . MustParse ( tabData . Messages [ i ] . Raw . Reply . To ) . Resourcepart ( ) ) )
} else {
author . SetText ( tabData . Messages [ i ] . Author )
}
@@ -160,6 +184,8 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
selectedId = id
}
scroller . CreateItem ( )
tabData . Scroller = scroller
chatTabs [ mucJidStr ] = tabData
@@ -169,81 +195,97 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
}
func dropToSignInPage ( reason string ) {
a = app . New ( )
w = a . NewWindow ( "Welcome to Pi" )
w . Resize ( fyne . NewSize ( 500 , 500 ) )
rt := widget . NewRichTextFromMarkdown ( "# Welcome to pi\nIt appears you do not have a valid account configured. Let's create one!" )
footer := widget . NewRichTextFromMarkdown ( fmt . Sprintf ( "Reason for being dropped to the sign-in page:\n\n```%s```" , reason ) )
userEntry := widget . NewEntry ( )
userEntry . SetPlaceHolder ( "Your JID" )
serverEntry := widget . NewEntry ( )
serverEntry . SetPlaceHolder ( "Server and port" )
passwordEntry := widget . NewPasswordEntry ( )
passwordEntry . SetPlaceHolder ( "Your Password" )
nicknameEntry := widget . NewEntry ( )
nicknameEntry . SetPlaceHolder ( "Your Nickname" )
w = a . NewWindow ( "Welcome to Pi" )
w . Resize ( fyne . NewSize ( 500 , 500 ) )
rt := widget . NewRichTextFromMarkdown ( "# Welcome to pi\nIt appears you do not have a valid account configured. Let's create one!" )
footer := widget . NewRichTextFromMarkdown ( fmt . Sprintf ( "Reason for being dropped to the sign-in page:\n\n```%s```" , reason ) )
userEntry := widget . NewEntry ( )
userEntry . SetPlaceHolder ( "Your JID" )
serverEntry := widget . NewEntry ( )
serverEntry . SetPlaceHolder ( "Server and port" )
passwordEntry := widget . NewPasswordEntry ( )
passwordEntry . SetPlaceHolder ( "Your Password" )
nicknameEntry := widget . NewEntry ( )
nicknameEntry . SetPlaceHolder ( "Your Nickname" )
userView := widget . NewFormItem ( "" , userEntry )
serverView := widget . NewFormItem ( "" , serverEntry )
passwordView := widget . NewFormItem ( "" , passwordEntry )
nicknameView := widget . NewFormItem ( "" , nicknameEntry )
items := [ ] * widget . FormItem {
serverView ,
userView ,
passwordView ,
nicknameView ,
}
userView := widget . NewFormItem ( "" , userEntry )
serverView := widget . NewFormItem ( "" , serverEntry )
passwordView := widget . NewFormItem ( "" , passwordEntry )
nicknameView := widget . NewFormItem ( "" , nicknameEntry )
items := [ ] * widget . FormItem {
serverView ,
userView ,
passwordView ,
nicknameView ,
}
btn := widget . NewButton ( "Create an account" , func ( ) {
dialog . ShowForm ( "Create an account" , "Create" , "Dismiss" , items , func ( b bool ) {
if b {
config := piConfig { }
config . Login . Host = serverEntry . Text
config . Login . User = userEntry . Text
config . Login . Password = passwordEntry . Text
config . Login . DisplayName = nicknameEntry . Text
config . Notifications = true
btn := widget . NewButton ( "Create an account" , func ( ) {
dialog . ShowForm ( "Create an account" , "Create" , "Dismiss" , items , func ( b bool ) {
if b {
config := piConfig { }
config . Login . Host = serverEntry . Text
config . Login . User = userEntry . Text
config . Login . Password = passwordEntry . Text
config . Login . DisplayName = nicknameEntry . Text
config . Notifications = true
config . Login . MucsToJoin = append ( config . Login . MucsToJoin , "ringen@muc.isekai.rocks" ) // DEBUG
bytes , err := json . MarshalIndent ( config , "" , " " )
if err != nil {
dialog . ShowError ( err , w )
return
}
os . Create ( "pi.json" )
os . WriteFile ( "pi.json" , bytes , os . FileMode ( os . O_RDWR ) ) // TODO: See if this works on non-unix like systems
a . SendNotification ( fyne . NewNotification ( "Done" , "Relaunch the application" ) )
w . Close ( )
bytes , err := xml . MarshalIndent ( config , "" , "\t " )
if err != nil {
dialog . ShowError ( err , w )
return
}
} , w )
} )
btn2 := widget . NewButton ( "Close pi" , func ( ) {
w . Close ( )
} )
w . SetContent ( container . NewVBox ( rt , btn , btn2 , footer ) )
w . ShowAndRun ( )
writer , err := a . Storage ( ) . Create ( "pi.xml" )
if err != nil {
dialog . ShowError ( err , w )
return
}
defer writer . Close ( )
_ , err = writer . Write ( bytes )
if err != nil {
dialog . ShowError ( err , w )
return
}
a . SendNotification ( fyne . NewNotification ( "Done" , "Relaunch the application" ) )
a . Quit ( )
//w.Close()
}
} , w )
} )
btn2 := widget . NewButton ( "Close pi" , func ( ) {
w . Close ( )
} )
w . SetContent ( container . NewVBox ( rt , btn , btn2 , footer ) )
w . ShowAndRun ( )
}
func main ( ) {
muc . Since ( time . Now ( ) )
config = piConfig { }
a = app . NewWithID ( "pi-ism" )
reader , err := a . Storage ( ) . Open ( "pi.xml" )
if err != nil {
dropToSignInPage ( err . Error ( ) )
return
}
defer reader . Close ( )
bytes , err := os . ReadFile ( "./pi.json" )
bytes , err := io . ReadAll ( reader )
if err != nil {
dropToSignInPage ( err . Error ( ) )
return
}
err = json . Unmarshal ( bytes , & config )
err = xml . Unmarshal ( bytes , & config )
if err != nil {
dropToSignInPage ( fmt . Sprintf ( "Your pi.json file is invalid:\n%s" , err . Error ( ) ) )
dropToSignInPage ( fmt . Sprintf ( "Your pi.xml file is invalid:\n%s" , err . Error ( ) ) )
return
}
login = config . Login
DMs = config . DMs
login = config . Login
notifications = config . Notifications
client , err := oasisSdk . CreateClient (
@@ -266,8 +308,10 @@ func main() {
for j , v := range s {
_ , err := url . Parse ( v )
if err == nil && strings . HasPrefix ( v , "https://" ) {
img = v
s [ j ] = fmt . Sprintf ( "[%s](%s)" , v , v )
if strings . HasSuffix ( v , ".png" ) || strings . HasSuffix ( v , ".jpg" ) || strings . HasSuffix ( v , ".jpeg" ) || strings . HasSuffix ( v , ".webp" ) || strings . HasSuffix ( v , ".mp4" ) {
img = v
}
}
}
lines [ i ] = strings . Join ( s , " " )
@@ -298,14 +342,30 @@ func main() {
} )
}
} ,
func ( client * oasisSdk . XmppClient , _ * muc . Channel , msg * oasisSdk . XMPPChatMessage ) {
func ( client * oasisSdk . XmppClient , muc * muc . Channel , msg * oasisSdk . XMPPChatMessage ) {
// HACK: IGNORING ALL MESSAGES FROM CLASSIC MUC HISTORY IN PREPARATION OF MAM SUPPORT
ignore := false
correction := false
for _ , v := range msg . Unknown {
if v . XMLName . Local == "delay" { // CLasic history message
//ignore = true
//fmt.Println("ignoring!")
}
}
for _ , v := range msg . Unknown {
if v . XMLName . Local == "replace" {
correction = true
}
}
var ImageID string = ""
mucJidStr := msg . From . Bare ( ) . String ( )
if tab , ok := chatTabs [ mucJidStr ] ; ok {
chatTabs [ mucJidStr ] . Muc = muc
str := * msg . CleanedBody
if notifications {
if strings . Contains ( str , login . DisplayName ) || ( msg . Reply != nil && strings . Contains ( msg . Reply . To , login . DisplayName ) ) {
if ! ignore && notifications {
if ! correction && strings . Contains ( str , login . DisplayName ) || ( msg . Reply != nil && strings . Contains ( msg . Reply . To , login . DisplayName ) ) {
a . SendNotification ( fyne . NewNotification ( fmt . Sprintf ( "Mentioned in %s" , mucJidStr ) , str ) )
}
}
@@ -317,7 +377,7 @@ func main() {
_ , err := url . Parse ( v )
if err == nil && strings . HasPrefix ( v , "https://" ) {
s [ j ] = fmt . Sprintf ( "[%s](%s)" , v , v )
if strings . HasSuffix ( v , ".png" ) || strings . HasSuffix ( v , ".jp" ) || strings . HasSuffix ( v , ".webp" ) {
if strings . HasSuffix ( v , ".png" ) || strings . HasSuffix ( v , ".jpg" ) || strings . HasSuffix ( v , ".jpeg ") || strings . HasSuffix ( v , ".webp" ) || strings . HasSuffix ( v , ".mp4" ) {
ImageID = v
}
}
@@ -334,15 +394,30 @@ func main() {
} else {
replyID = msg . Reply . To
}
if correction {
for i := len ( tab . Messages ) - 1 ; i > 0 ; i -- {
if tab . Messages [ i ] . Raw . From . String ( ) == msg . From . String ( ) {
tab . Messages [ i ] . Content = * msg . CleanedBody + " (edited)"
fyne . Do ( func ( ) {
tab . Scroller . Refresh ( )
} )
return
}
}
}
myMessage := Message {
Author : msg . From . Resourcepart ( ) ,
Content : str ,
ID : msg . ID ,
ReplyID : replyID ,
Raw : * msg ,
Author : msg . From . Resourcepart ( ) ,
Content : str ,
ID : msg . ID ,
ReplyID : replyID ,
Raw : * msg ,
ImageURL : ImageID ,
}
tab . Messages = append ( tab . Messages , myMessage )
if ! ignore {
tab . Messages = append ( tab . Messages , myMessage )
}
fyne . Do ( func ( ) {
tab . Scroller . Refresh ( )
if scrollDownOnNewMessage {
@@ -353,12 +428,27 @@ func main() {
} ,
func ( _ * oasisSdk . XmppClient , from jid . JID , state oasisSdk . ChatState ) {
switch state {
case oasisSdk . ChatStateActive :
case oasisSdk . ChatStateComposing :
fyne . Do ( func ( ) {
statBar . SetText ( fmt . Sprintf ( "%s is typing..." , from . Resourcepart ( ) ) )
} )
case oasisSdk . ChatStatePaused :
fyne . Do ( func ( ) {
statBar . SetText ( fmt . Sprintf ( "%s has stoped typing." , from . Resourcepart ( ) ) )
} )
case oasisSdk . ChatStateInactive :
fyne . Do ( func ( ) {
statBar . SetText ( fmt . Sprintf ( "%s is idle" , from . Resourcepart ( ) ) )
} )
case oasisSdk . ChatStateGone :
fyne . Do ( func ( ) {
statBar . SetText ( fmt . Sprintf ( "%s is gone" , from . Resourcepart ( ) ) )
} )
default :
fyne . Do ( func ( ) {
statBar . SetText ( fmt . Sprint ( "Unknown state: " , state ) )
} )
}
} ,
func ( _ * oasisSdk . XmppClient , from jid . JID , id string ) {
@@ -368,11 +458,33 @@ func main() {
fmt . Printf ( "%s has seen %s" , from . String ( ) , id )
} ,
)
if err != nil {
log . Fatalln ( "Could not create client - " + err . Error ( ) )
}
/ *
client . Session . Serve ( xmpp . HandlerFunc ( func ( t xmlstream . TokenReadEncoder , start * xml . StartElement ) error {
d := xml . NewTokenDecoder ( t )
// Ignore anything that's not a message.
if start . Name . Local != "message" {
return nil
}
msg := struct {
stanza . Message
Body string ` xml:"body" `
} { }
err := d . DecodeElement ( & msg , start )
if err != nil {
return err
}
if msg . Body != "" {
log . Println ( "Got message: %q" , msg . Body )
}
return nil
} ) )
* /
go func ( ) {
for connection {
err = client . Connect ( )
@@ -400,7 +512,7 @@ func main() {
entry . OnChanged = func ( s string ) {
}
sendbtn := widget . NewButton ( "Send" , func ( ) {
SendCallback := func ( ) {
text := entry . Text
if tabs . Selected ( ) == nil || tabs . Selected ( ) . Content == nil {
return
@@ -426,14 +538,13 @@ func main() {
}
go func ( ) {
//TODO: Fix message hack until jjj adds message sending
if replying {
m := chatTabs [ activeMucJid ] . Messages [ selectedId ] . Raw
client . ReplyToEvent ( & m , text )
return
}
err = client . SendText ( jid . MustParse ( activeMucJid ) , text )
err = client . SendText ( jid . MustParse ( activeMucJid ) . Bare ( ) , text )
if err != nil {
dialog . ShowError ( err , w )
}
@@ -443,6 +554,7 @@ func main() {
chatTabs [ activeMucJid ] . Messages = append ( chatTabs [ activeMucJid ] . Messages , Message {
Author : "You" ,
Content : text ,
ReplyID : "PICLIENT:UNAVAILABLE" ,
} )
fyne . Do ( func ( ) {
if scrollDownOnNewMessage {
@@ -451,17 +563,23 @@ func main() {
} )
}
entry . SetText ( "" )
} )
}
sendbtn := widget . NewButton ( "Send" , SendCallback )
entry . OnSubmitted = func ( s string ) {
SendCallback ( )
// i fucking hate fyne
}
mit := fyne . NewMenuItem ( "about pi" , func ( ) {
dialog . ShowInformation ( "about pi" , fmt . Sprintf ( "the XMPP client from hell\n\npi is an experimental XMPP client\nwritten by Sunglocto in Go.\n\nVersion %s" , version ) , w )
} )
reconnect := fyne . NewMenuItem ( "reconnect" , func ( ) {
go func ( ) {
go func ( ) {
err := client . Connect ( )
if err != nil {
fyne . Do ( func ( ) {
fyne . Do ( func ( ) {
dialog . ShowError ( err , w )
} )
}
@@ -510,85 +628,117 @@ func main() {
}
selectedScroller . ScrollToTop ( )
} )
/ * mib := fyne . NewMenuItem ( "Join a room" , func ( ) {
nickEntry := widget . NewEntry ( )
nickEntry . SetText ( login . DisplayName )
roomEntry := widget . NewEntry ( )
items := [ ] * widget . FormItem {
widget . NewFormItem ( "Nick" , nickEntry ) ,
widget . NewFormItem ( "MUC address" , roomEntry ) ,
w . SetOnDropped ( func ( p fyne . Position , u [ ] fyne . URI ) {
var link string
myUri := u [ 0 ] // Only upload a single file
progress := make ( chan oasisSdk . UploadProgress )
myprogressbar := widget . NewProgressBar ( )
diag := dialog . NewCustom ( "Uploading file" , "Hide" , myprogressbar , w )
diag . Show ( )
go func ( ) {
client . UploadFile ( client . Ctx , myUri . Path ( ) , progress )
} ( )
for update := range progress {
fyne . Do ( func ( ) {
myprogressbar . Value = float64 ( update . Percentage ) / 100
myprogressbar . Refresh ( )
} )
if update . Error != nil {
diag . Dismiss ( )
dialog . ShowError ( update . Error , w )
return
}
if update . GetURL != "" {
link = update . GetURL
}
}
dialog . ShowForm ( "join a MUC" , "join" , "cancel" , items , func ( b bool ) {
if b {
roomJid , err := jid . Parse ( roomEntry . Text )
diag . Dismiss ( )
a . Clipboard ( ) . SetContent ( link )
dialog . ShowInformation ( "file successfully uploaded\nURL copied to your clipboard" , link , w )
} )
deb := fyne . NewMenuItem ( "DEBUG: Attempt to get MAM history from a user" , func ( ) {
//res, err := history.Fetch(client.Ctx, history.Query{}, jid.MustParse("ringen@muc.isekai.rocks"), client.Session)
} )
mic := fyne . NewMenuItem ( "upload a file" , func ( ) {
var link string
var toperr error
//var topreader fyne.URIReadCloser
dialog . ShowFileOpen ( func ( reader fyne . URIReadCloser , err error ) {
go func ( ) {
if err != nil {
dialog . ShowError ( err , w )
return
}
nick : = nickEntry . Text
go func ( ) {
// We probably don't need to handle the error here, if it fails the user will know
_ , err := client . MucClient . Join ( client . Ctx , roomJid , client . Session , nil )
if err != nil {
panic ( err )
}
} ( )
addChatTab ( true , roomJid , nick )
}
} , w )
} ) * /
if reader = = nil {
return
}
bytes , toperr = io . ReadAll ( reader )
//topreader = reader
mic := fyne . NewMenuItem ( "upload a file" , func ( ) {
var link string
var bytes [ ] byte
var toperr error
var topreader fyne . URIReadCloser
dialog . ShowFileOpen ( func ( reader fyne . URIReadCloser , err error ) {
if err != nil {
dialog . ShowError ( err , w )
return
}
if reader == nil {
return
}
bytes , toperr = io . ReadAll ( reader )
topreader = reader
if toperr != nil {
dialog . ShowError ( toperr , w )
return
}
progress := make ( chan oasisSdk . UploadProgress )
myprogressbar := widget . NewProgressBar ( )
dialog . ShowCustom ( "Uploading file" , "Hide" , myprogressbar , w )
go func ( ) {
client . UploadFileFromBytes ( client . Ctx , topreader . URI ( ) . Name ( ) , bytes , progress )
} ( )
for update := range progress {
myprogressbar . Value = float64 ( update . Percentage )
myprogressbar . Refresh ( )
if update . Error != nil {
dialog . ShowError ( update . Error , w )
if toperr != nil {
dialog . ShowError ( toperr , w )
return
}
if update . GetURL ! = "" {
link = update . GetURL
}
}
progress : = make ( chan oasisSdk . UploadProgress )
myprogressbar := widget . NewProgressBar ( )
diag := dialog . NewCustom ( "Uploading file" , "Hide" , myprogressbar , w )
diag . Show ( )
go func ( ) {
client . UploadFile ( client . Ctx , reader . URI ( ) . Path ( ) , progress )
} ( )
a . Clipboard ( ) . SetContent ( link )
dialog . ShowInformation ( "file successfully uploaded\nURL copied to your clipboard" , link , w )
for update := range progress {
fyne . Do ( func ( ) {
myprogressbar . Value = float64 ( update . Percentage ) / 100
myprogressbar . Refresh ( )
} )
if update . Error != nil {
diag . Dismiss ( )
dialog . ShowError ( update . Error , w )
return
}
if update . GetURL != "" {
link = update . GetURL
}
}
diag . Dismiss ( )
a . Clipboard ( ) . SetContent ( link )
dialog . ShowInformation ( "file successfully uploaded\nURL copied to your clipboard" , link , w )
} ( )
} , w )
} )
menu_help := fyne . NewMenu ( "π " , mit , reconnect )
menu_changeroom := fyne . NewMenu ( "β" , mic )
servDisc := fyne . NewMenuItem ( "Service discovery " , func ( ) {
myBox := container . NewVBox ( )
info , err := disco . GetInfo ( client . Ctx , "" , jid . MustParse ( "ringen@muc.isekai.rocks" ) , client . Session )
if err != nil {
dialog . ShowError ( err , w )
}
m := info . Features
for _ , v := range m {
myBox . Objects = append ( myBox . Objects , widget . NewLabel ( v . Var ) )
myBox . Refresh ( )
}
dialog . ShowCustom ( "things" , "cancel" , myBox , w )
} )
menu_help := fyne . NewMenu ( "π" , mit , reconnect , deb )
menu_changeroom := fyne . NewMenu ( "β" , mic , servDisc )
menu_configureview := fyne . NewMenu ( "γ " , mia , mis , jtt , jtb )
bit := fyne . NewMenuItem ( "mark selected message as read" , func ( ) {
selectedScroller , ok := tabs . Selected ( ) . Content . ( * widget . List )
@@ -628,7 +778,7 @@ func main() {
}
m := chatTabs [ activeChatJid ] . Messages [ selectedId ] . Raw
bytes , err := xml . MarshalIndent ( m , "" , " " )
bytes , err := xml . MarshalIndent ( m , "" , "\t " )
if err != nil {
dialog . ShowError ( err , w )
return
@@ -644,13 +794,7 @@ func main() {
tabs = container . NewAppTabs (
container . NewTabItem ( "τίποτα" , widget . NewLabel ( `
welcome to pi
you are currently not focused on any rooms .
you can add new rooms by editing your pi . json file .
in order to change application settings , refer to the tab - menu with the Greek letters .
these buttons allow you to configure the application as well as other functions .
for more information about the pi project itself , hit the π button .
pi
` ) ) ,
)
@@ -669,6 +813,34 @@ func main() {
}
}
w . SetContent ( container . NewVSplit ( container . NewVSplit ( tabs , container . NewHSplit ( entry , sendbtn ) ) , widget . NewLabel ( "pi" ) ) )
tabs . OnSelected = func ( ti * container . TabItem ) {
selectedScroller , ok := tabs . Selected ( ) . Content . ( * widget . List )
if ! ok {
return
}
var activeChatJid string
for jid , tabData := range chatTabs {
if tabData . Scroller == selectedScroller {
activeChatJid = jid
break
}
}
tab := chatTabs [ activeChatJid ]
if tab . isMuc {
chatInfo = * container . NewHBox ( widget . NewLabel ( tab . Muc . Addr ( ) . String ( ) ) )
} else {
chatInfo = * container . NewHBox ( widget . NewLabel ( tab . Jid . String ( ) ) )
}
if tab . isMuc {
chatSidebar = * container . NewStack ( container . NewVScroll ( container . NewVBox ( widget . NewRichTextFromMarkdown ( fmt . Sprintf ( "# %s" , tab . Jid . String ( ) ) ) , widget . NewRichTextFromMarkdown ( tab . Muc . Addr ( ) . String ( ) ) ) ) )
//chatSidebar.Refresh()
}
}
statBar . SetText ( "nothing seems to be happening right now..." )
w . SetContent ( container . NewVSplit ( container . NewVSplit ( container . NewHSplit ( tabs , & chatSidebar ) , container . NewHSplit ( entry , sendbtn ) ) , container . NewHSplit ( & statBar , & chatInfo ) ) )
w . ShowAndRun ( )
}