You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

527 lines
16 KiB

package ui
import (
. "gotoot/toot"
"log"
"html"
"strconv"
"github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
)
// Structure mapping out Toot-Interact Widgets
type TootUi struct {
ReplyBtn *gtk.Button // reply button
ReblogBtn *gtk.ToggleButton // Reblog button
FavBtn *gtk.ToggleButton // Favourite button
Container *gtk.Box // Reference to the row to be delete'd on Delete
Status TootStatus // Reference to the Status (mainly for getting updates using the ID)
}
func uiHashtag ( bx *gtk.Grid, tag TootTag, index int ) {
hashtag, _ := gtk.ButtonNewWithLabel ( tag.Name )
bx.Attach ( hashtag, 1, index, 1,1 )
if len(tag.History) >= 0 {
accounts, _ := gtk.LabelNew ( tag.History[0].Accounts + " Accounts" )
bx.Attach ( accounts, 2, index, 1,1 )
uses, _ := gtk.LabelNew ( tag.History[0].Uses + " Uses" )
bx.Attach ( uses, 3, index, 1,1 )
}
}
func HashtagListing ( page *gtk.Box, tags []TootTag, placeholder *gtk.Box ) {
bx, _ := gtk.GridNew()
bx.SetHAlign ( gtk.ALIGN_FILL )
bx.SetRowSpacing (5)
bx.SetBorderWidth ( 10 )
for k, tag := range tags {
uiHashtag ( bx, tag, k )
}
page.Add ( bx )
if placeholder != nil {
placeholder.Hide()
placeholder.Destroy()
}
page.ShowAll()
}
var tootMap map[string]TootUi
func TootListing ( page *gtk.Box, toots []TootStatus, placeholder *gtk.Box ) {
for _, fx := range toots {
r, _ := gtk.BoxNew ( gtk.ORIENTATION_VERTICAL, 0)
if fx.Muted == false {
r.Add ( uiToot ( page, &fx ) )
} else {
lbl, _ := gtk.LabelNew ("Muted")
r.Add ( lbl )
}
page.Add ( r )
}
if placeholder != nil {
placeholder.Hide()
placeholder.Destroy()
}
page.ShowAll()
}
func NotificationsListing ( page *gtk.Box, noti []TootNotification, placeholder *gtk.Box ) {
var lbl *gtk.Label
var logo, ava *gtk.Image
var IconName string
for _, n := range noti {
b, _ := gtk.BoxNew ( gtk.ORIENTATION_VERTICAL, 0 )
hbox, _ := gtk.BoxNew ( gtk.ORIENTATION_HORIZONTAL, 0 )
evbox, _ := gtk.EventBoxNew ()
px, _ := pixVariantGetPixbuf ( n.Account.Avatar, 16 )
ava, _ = gtk.ImageNewFromPixbuf ( px )
switch n.Type {
case "follow":
lbl, _ = gtk.LabelNew ( DisplayName ( n.Account ) + " followed you" )
IconName = "contact-new"
// status should be nil in this case
case "mention":
lbl, _ = gtk.LabelNew ( DisplayName ( n.Account ) + " mentioned you" )
IconName = "mail-replied"
case "reblog":
lbl, _ = gtk.LabelNew ( DisplayName ( n.Account ) + " boosted one of your toots" )
IconName = "mail-send-receive"
case "favourite":
lbl, _ = gtk.LabelNew ( DisplayName ( n.Account ) + " fav'd one of your toots" )
IconName = "emblem-favorite"
case "poll":
lbl, _ = gtk.LabelNew ( "A poll ended you took place in" )
IconName = "task-due"
}
logo, _ = gtk.ImageNewFromIconName ( IconName, gtk.ICON_SIZE_MENU )
lbl.SetHAlign ( gtk.ALIGN_START )
ava.SetMarginStart ( 5 )
ava.SetMarginEnd( 5 )
logo.SetMarginStart ( 10 )
hbox.Add ( logo )
hbox.Add ( ava )
hbox.Add ( lbl )
evbox.Add ( hbox )
evbox.SetEvents ( gdk.BUTTON1_MASK )
evbox.Connect ("button-press-event", CbShowAccount, n.Account.Id )
b.Add ( evbox )
if n.Status != nil {
b.Add ( uiToot ( page, n.Status ) )
}
b.SetMarginBottom ( 5 )
b.SetMarginTop ( 5 )
page.Add ( b )
}
if placeholder != nil {
placeholder.Hide()
placeholder.Destroy()
}
page.ShowAll()
}
func uiCard ( card TootCard, width int ) *gtk.Frame {
frame, _ := gtk.FrameNew ( "Card: " + card.Title )
frame.SetBorderWidth ( 10 )
box, _ := gtk.BoxNew ( gtk.ORIENTATION_VERTICAL, 2 )
descr, _ := gtk.LabelNew ( card.Description )
descr.SetHAlign ( 0 )
descr.SetSingleLineMode ( false )
descr.SetLineWrap ( true )
box.Add ( descr )
link, _ := gtk.LabelNew ( "" )
link.SetHAlign ( 0 )
link.SetMarginTop ( 5 )
link.SetMarginBottom ( 5 )
link.SetMarkup ( "<a href=\"" + html.EscapeString(card.Url) + "\">Visit Url</a>" )
box.Add ( link )
if card.Image != "" {
px, err := pixVariantGetPixbuf ( card.Image, width )
if err != nil {
return nil
}
image, _ := gtk.ImageNewFromPixbuf ( px )
image.SetMarginBottom ( 5 )
box.Add ( image )
}
frame.Add ( box )
return frame
}
func uiMedia ( media []TootAttachment ) *gtk.FlowBox {
fb, _ := gtk.FlowBoxNew ()
fb.SetHAlign ( gtk.ALIGN_FILL )
var im []*gtk.Image
for range media {
i, _ := gtk.ImageNew ()
fb.Add ( i )
im = append ( im, i )
}
for k, v := range media {
if k >= len(im) {
break
}
go func () {
px, err := pixVariantGetPixbuf ( v.PreviewUrl, 100 )
if err != nil {
return
}
glib.IdleAdd ( func() {
im[k].SetFromPixbuf ( px )
})
}()
}
return fb
}
func uiPoll ( poll TootPoll ) *gtk.Grid {
b, _ := gtk.GridNew ()
b.SetHAlign ( gtk.ALIGN_FILL )
b.SetRowSpacing (5)
b.SetBorderWidth ( 10 )
for k, o := range poll.Options {
l, _ := gtk.LabelNew ( strconv.Itoa(k+1) + "." )
l.SetMarginEnd ( 5 )
b.Attach ( l, 0, k, 1,1 )
if poll.Expired == true || poll.Voted == true {
l, _ := gtk.LabelNew ( o.Title )
l.SetXAlign ( 0 )
l.SetHAlign ( gtk.ALIGN_FILL )
l.SetMarginEnd ( 5 )
b.Attach ( l, 1, k, 1,1 )
var percentage string = strconv.FormatFloat ( float64(o.VotesCount) / float64(poll.VotesCount) * 100.0, 'f', 2, 64)
l2, _ := gtk.LabelNew ("(" + percentage + "%)" )
l2.SetXAlign ( 1 )
b.Attach ( l2, 2, k, 1,1 )
} else {
l, _ := gtk.ButtonNewWithLabel ( o.Title )
b.Attach ( l, 1, k, 2,1 )
}
}
if poll.HideTotals == false || poll.Expired == true || poll.Voted == true{
l, _ := gtk.LabelNew ( "Total: " + strconv.Itoa ( poll.VotesCount ))
l.SetXAlign ( 0 )
b.Attach ( l, 0, len ( poll.Options ), 3,1 )
}
return b
}
func UpdateTootUi ( tootui TootUi ) {
tootui.Status.GetUpdate()
tootui.FavBtn.SetLabel ( strconv.Itoa ( tootui.Status.FavouritesCount ))
tootui.ReblogBtn.SetLabel ( strconv.Itoa ( tootui.Status.ReblogsCount ))
tootui.ReplyBtn.SetLabel ( strconv.Itoa ( tootui.Status.RepliesCount ))
}
func DoFav ( b interface{}, id string ) {
tootui := tootMap[id]
if tootui.Status.Favourited == true {
tootui.Status.PostUnfavourite()
} else {
tootui.Status.PostFavourite()
}
UpdateTootUi ( tootui )
// ToDo: notification?
}
func DoReblog ( b interface{}, id string ) {
tootui := tootMap[id]
if tootui.Status.Reblogged == true {
tootui.Status.PostUnreblog()
} else {
tootui.Status.PostReblog()
}
UpdateTootUi ( tootui )
// ToDo: notification?
}
func DoDelete ( b interface{}, id string ) {
tootui := tootMap[id]
log.Println ("Deleting toot", tootui.Status.Id, tootui.Status.Account.Acct, tootui.Status.Content )
//err := tootui.Status.DeleteStatus()
//if err != nil {
// log.Println ( "Cannot delete Status", err )
// return
//}
tootui.Container.Destroy()
delete ( tootMap, id )
// ToDo: notification?
}
func DoReply ( b interface{}, id string ) {
ReplyTootWindow ( tootMap[id].Status )
}
func DisplayName ( acc TootAccount ) string {
if acc.DisplayName == "" {
return acc.Username
}
return acc.DisplayName
}
func AccountName ( acc TootAccount, avaWidth int, widgets... *gtk.Widget ) ( *gtk.EventBox, error ) {
avaImage, _ := gtk.ImageNew ()
go func () {
if avaWidth <= 0 {
avaWidth = 42
}
ava, err := pixVariantGetPixbuf ( acc.Avatar, avaWidth )
if err != nil {
return
}
glib.IdleAdd ( func() {
avaImage.SetFromPixbuf ( ava )
} )
}()
dr := DisplayName ( acc )
DispName, err := gtk.LabelNew ( "" )
DispName.SetMarkup ("<b>" + html.EscapeString (dr) + "</b>" )
if err != nil {
return nil, err
}
DispName.SetHExpand ( false )
DispName.SetHAlign ( gtk.ALIGN_START )
Handle, err := gtk.LabelNew ( "" )
if err != nil {
return nil, err
}
Handle.SetMarkup ( "<i>@" + acc.Acct + "</i>" )
Handle.SetXAlign ( 0 )
vbox, err := gtk.BoxNew ( gtk.ORIENTATION_VERTICAL, 2 )
if err != nil {
return nil, err
}
vbox.Add ( DispName )
vbox.Add ( Handle )
vbox.SetMarginStart ( 5 )
vbox.SetHAlign ( gtk.ALIGN_FILL )
header, err := gtk.BoxNew ( gtk.ORIENTATION_HORIZONTAL, 2 )
if err != nil {
return nil, err
}
header.SetHAlign ( gtk.ALIGN_FILL )
header.Add ( avaImage )
header.Add ( vbox )
if acc.Bot == true {
bot, err := gtk.ImageNewFromIconName ( "computer" , gtk.ICON_SIZE_BUTTON )
if err != nil {
return nil, err
}
bot.SetMarginStart ( 5 )
header.Add ( bot )
}
if acc.Locked == true {
locked, err := gtk.ImageNewFromIconName ( "system-lock-screen" , gtk.ICON_SIZE_BUTTON )
if err != nil {
return nil, err
}
locked.SetMarginStart ( 5 )
header.Add ( locked )
}
for i := len(widgets)-1; i>=0; i-- {
w := widgets[i]
if w != nil {
header.PackEnd ( w, false, false, 5 )
}
}
evBox, _ := gtk.EventBoxNew()
evBox.Add ( header )
evBox.SetEvents ( gdk.BUTTON1_MASK )
evBox.Connect ( "button-press-event", CbShowAccount, acc.Id )
return evBox, nil
}
func CbShowAccount ( w interface{}, e interface{}, id string ) {
log.Println ("Show Account: ", id )
a := TootAccount { Id: id }
acc, err := a.Get()
if err == nil {
log.Println ("Name: ", DisplayName ( *acc ) )
uiAccountView ( *acc, &MainWin.OtherAccount )
MainWin.OtherAccountLabel.SetText ( DisplayName ( *acc ) )
w, _ := MainWin.Notebook.GetNthPage ( OtherAccountPage )
w.ToWidget().ShowAll()
MainWin.Notebook.SetCurrentPage ( OtherAccountPage )
}
}
func uiTootReblogged ( status *TootStatus, tui *TootUi ) *gtk.EventBox {
reblogbox, _ := gtk.BoxNew ( gtk.ORIENTATION_HORIZONTAL, 4 )
retootImg, _ := gtk.ImageNewFromIconName ( "mail-send-receive", gtk.ICON_SIZE_MENU )
reblogbox.Add ( retootImg )
rebloggedBy, _ := gtk.LabelNew ( "Reblogged by " )
rebloggedBy.SetXAlign ( 0 )
reblogbox.Add ( rebloggedBy )
px, _ := pixVariantGetPixbuf ( status.Account.Avatar, 16 )
img, _ := gtk.ImageNewFromPixbuf ( px )
reblogbox.Add ( img )
lbl, _ := gtk.LabelNew ( DisplayName ( *status.Account ) )
lbl.SetHAlign ( gtk.ALIGN_START )
reblogbox.Add ( lbl )
evBox, _ := gtk.EventBoxNew ()
evBox.SetEvents ( gdk.BUTTON1_MASK )
evBox.SetAboveChild ( true )
evBox.Add ( reblogbox )
evBox.Connect ( "button-press-event", CbShowAccount, status.Account.Id )
//evBox.ShowAll()
//w, _ := evBox.GetWindow()
//disp, _ := gdk.DisplayGetDefault()
//crs, _ := gdk.CursorNewFromName ( disp, "pointer" )
//w.SetCursor ( crs )
return evBox
}
func uiToot ( box *gtk.Box, status *TootStatus ) *gtk.Box {
container, _ := gtk.BoxNew ( gtk.ORIENTATION_VERTICAL, 3 )
container.SetBorderWidth ( 10 )
tui := new(TootUi)
tui.Status = *status
if status.Reblog != nil {
reblogbox := uiTootReblogged ( status, tui )
if reblogbox != nil {
container.Add ( reblogbox )
}
status = status.Reblog
}
timelabel, _ := gtk.LabelNew ( status.CreatedAt.Format("02 Jan 2006") )
timelabel.SetHAlign ( gtk.ALIGN_END )
timelabel.SetXAlign ( 1.0 )
header, err := AccountName ( *status.Account, 42, timelabel.ToWidget() )
if err != nil {
return nil
}
container.Add ( header )
Text, _ := gtk.LabelNew ( "" )
r, err := htmlToPango(status.Content)
if err == nil {
Text.SetMarkup ( r )
} else {
log.Println ( "HTML ERROR", err )
}
Text.SetSingleLineMode ( false )
Text.SetLineWrap ( true )
Text.SetXAlign ( 0 )
if status.SpoilerText != "" {
exp, _ := gtk.ExpanderNew (status.SpoilerText)
exp.Add ( Text )
container.Add ( exp )
} else {
container.Add ( Text )
}
if status.Poll != nil {
pollcontainer := uiPoll ( *status.Poll )
if pollcontainer != nil {
container.Add ( pollcontainer )
}
}
if len( status.MediaAttachments ) > 0 {
mediacontainer := uiMedia ( status.MediaAttachments )
if mediacontainer != nil {
container.Add ( mediacontainer )
}
}
if status.Card != nil {
cardcontainer := uiCard ( *status.Card, 200 )
if cardcontainer != nil {
container.Add ( cardcontainer )
}
}
iBox, _ := gtk.BoxNew ( gtk.ORIENTATION_HORIZONTAL, 0 )
Reply, _ := gtk.ButtonNewWithLabel ( strconv.Itoa (status.RepliesCount) )
Reblog, _ := gtk.ToggleButtonNewWithLabel ( strconv.Itoa (status.ReblogsCount) )
Fav, _ := gtk.ToggleButtonNewWithLabel ( strconv.Itoa (status.FavouritesCount) )
retootImg, _ := gtk.ImageNewFromIconName ( "mail-send-receive", gtk.ICON_SIZE_BUTTON )
favImg, _ := gtk.ImageNewFromIconName ( "emblem-favorite", gtk.ICON_SIZE_BUTTON )
replyImg, _ := gtk.ImageNewFromIconName ( "mail-replied", gtk.ICON_SIZE_BUTTON )
Reply.SetImage ( replyImg )
Reblog.SetImage ( retootImg )
Fav.SetImage ( favImg )
Reply.SetAlwaysShowImage ( true )
Reblog.SetAlwaysShowImage ( true )
Fav.SetAlwaysShowImage ( true )
Reply.SetImagePosition ( gtk.POS_RIGHT )
Reblog.SetImagePosition ( gtk.POS_RIGHT )
Fav.SetImagePosition ( gtk.POS_RIGHT )
if status.Favourited {
Fav.SetActive( true )
Fav.SetTooltipText ( "Unfav" )
} else {
Fav.SetActive( false )
Fav.SetTooltipText ( "Fav" )
}
if status.Visibility == "direct" || status.Visibility == "private" {
Reblog.SetSensitive ( false )
} else {
if status.Reblogged {
Reblog.SetActive ( true )
Reblog.SetTooltipText ( "Retract Retoot" )
} else {
Reblog.SetActive ( false )
Reblog.SetTooltipText ( "Retoot" )
}
}
iBox.Add ( Reply )
iBox.Add ( Reblog )
iBox.Add ( Fav )
tui.FavBtn = Fav
tui.ReblogBtn = Reblog
tui.ReplyBtn = Reply
tui.Container = container
tootMap[status.Id] = *tui
if MainWin.Account.Acc != nil && status.Account.Url == MainWin.Account.Acc.Url {
delImg, _ := gtk.ImageNewFromIconName ( "edit-delete", gtk.ICON_SIZE_BUTTON )
Del, _ := gtk.ButtonNewWithLabel ( "Delete" )
Del.SetImage ( delImg )
Del.SetAlwaysShowImage ( true )
Del.SetTooltipText ( "Delete Toot" )
Del.Connect ( "clicked", DoDelete, status.Id )
iBox.Add ( Del )
}
container.Add ( iBox )
Fav.Connect("clicked", DoFav, status.Id)
Reblog.Connect("clicked", DoReblog, status.Id)
Reply.Connect("clicked", DoReply, status.Id)
container.ShowAll()
return container
}