Added menu buttons and several tweaks

This commit is contained in:
diamondburned 2020-07-15 22:41:21 -07:00 committed by diamondburned
parent e65dbb20ed
commit cebc5f58b4
31 changed files with 490 additions and 213 deletions

2
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/Xuanwo/go-locale v0.2.0
github.com/alecthomas/chroma v0.7.3
github.com/diamondburned/cchat v0.0.43
github.com/diamondburned/cchat-discord v0.0.0-20200714063838-0a590627268b
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972
github.com/disintegration/imaging v1.6.2

56
go.sum
View File

@ -44,64 +44,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/diamondburned/aqs v0.0.0-20200704043812-99b676ee44eb h1:Ja/niwykeFoSkYxdRRzM8QUAuCswfLmaiBTd2UIU+54=
github.com/diamondburned/aqs v0.0.0-20200704043812-99b676ee44eb/go.mod h1:q1MbMBfZrv7xqV8n7LgMwhHs3oBbNwWJes8exs2AmDs=
github.com/diamondburned/arikawa v0.9.5 h1:P1ffsp+NHT22wWKYFVC8CdlGRLzPuUV9FcCBKOCJpCI=
github.com/diamondburned/arikawa v0.9.5/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
github.com/diamondburned/arikawa v0.9.6 h1:6TpfTKa2btoVQGxojNqv8g2YC0tIc/tX5w/OCVZPF5Q=
github.com/diamondburned/arikawa v0.9.6/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
github.com/diamondburned/arikawa v0.10.2 h1:xTsFWlWwGzFr8HD7tyv2jMRyserOR4yV5dhq/PZMPAA=
github.com/diamondburned/arikawa v0.10.2/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
github.com/diamondburned/cchat v0.0.40 h1:38gPyJnnDoNDHrXcV8Qchfv3y6jlS3Fzz/6FY0BPH6I=
github.com/diamondburned/cchat v0.0.40/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
github.com/diamondburned/cchat v0.0.41 h1:6y32s2wWTiDw4hWN/Gna6ay3uUrRAW5V8Cj0/xLKovw=
github.com/diamondburned/cchat v0.0.41/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
github.com/diamondburned/cchat v0.0.42 h1:FVMLy9hOTxKju8OWDBIStrekbgTHCaH8+GVnV4LOByg=
github.com/diamondburned/cchat v0.0.42/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
github.com/diamondburned/arikawa v0.10.5 h1:o5lBopooA+8cXlKZdct5qF0xztuZZ35phvQrwGS5vYM=
github.com/diamondburned/arikawa v0.10.5/go.mod h1:nIhVIatzTQhPUa7NB8w4koG1RF9gYbpAr8Fj8sKq660=
github.com/diamondburned/cchat v0.0.43 h1:HetAujSaUSdnQgAUZgprNLARjf/MSWXpCfWdvX2wOCU=
github.com/diamondburned/cchat v0.0.43/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
github.com/diamondburned/cchat-discord v0.0.0-20200703190659-fbf95b9b6c03 h1:F5TL7GPRU/D4ldVkS0haY3SiHPtf1Kby/4nbYpm//MQ=
github.com/diamondburned/cchat-discord v0.0.0-20200703190659-fbf95b9b6c03/go.mod h1:p0X6QUH0mxK8yEW0+a4QA77ClAmoxz8CvgbnobMtWQA=
github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03 h1:Xx4ioFTurT6qTxzTL8QlsH3E5VskLxHPJ8RwmaKhObA=
github.com/diamondburned/cchat-discord v0.0.0-20200708083530-d0e43cc63b03/go.mod h1:hjaCeQfhSqANH+ZtxXPMWzpgobb4syy2VcqDK4PXcPU=
github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b h1:c01OHXPE/HXcZdaPDgNNzmz3xN7MC0yIyL5i+6qILAc=
github.com/diamondburned/cchat-discord v0.0.0-20200708091545-601e8abeb23b/go.mod h1:IDBm0RqdQUASjPGP/RQkhmC1nGhGTTFoaHn9NbD/l7I=
github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6 h1:Nc9B6i7siInzh/uu3bTu7mcw7ckcFDTu4TtSNoxUMis=
github.com/diamondburned/cchat-discord v0.0.0-20200708212314-e08c31b895a6/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8=
github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c h1:4F7IJqMfpF02/j6ZbVO9x29rWExQncLHFbxZrsAhhvM=
github.com/diamondburned/cchat-discord v0.0.0-20200709041349-1e137df6de2c/go.mod h1:QHPtnxNrnMFCYB/b9kUP93D30Kf3AuGmkM91tScIpB8=
github.com/diamondburned/cchat-discord v0.0.0-20200711215912-d1f5376e30b3 h1:uMrWIWdI3qGY8IdH8LUyAM9pflcPvgpLTREOI0Qn7rw=
github.com/diamondburned/cchat-discord v0.0.0-20200711215912-d1f5376e30b3/go.mod h1:TP5/aE708Ae2quG1yAvCCoDJjKuBcjFZ1LYVw60FbTw=
github.com/diamondburned/cchat-discord v0.0.0-20200711221358-e50f72a01d0a h1:0/j7J0HTR3OuU0qaQ9HWLK0DlJdXPL72ziFVD1f6d8E=
github.com/diamondburned/cchat-discord v0.0.0-20200711221358-e50f72a01d0a/go.mod h1:TP5/aE708Ae2quG1yAvCCoDJjKuBcjFZ1LYVw60FbTw=
github.com/diamondburned/cchat-discord v0.0.0-20200712063736-b8c92a56d89b h1:W5Mmf6GagIctMH5n2TYORSENMZdJm8th4JrEJubLb60=
github.com/diamondburned/cchat-discord v0.0.0-20200712063736-b8c92a56d89b/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
github.com/diamondburned/cchat-discord v0.0.0-20200714012913-3c1d6f1d3f17 h1:dUF7+pmzfAKS1gBr5SySg5xkSrAmzRGcghDRjXl5TDg=
github.com/diamondburned/cchat-discord v0.0.0-20200714012913-3c1d6f1d3f17/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
github.com/diamondburned/cchat-discord v0.0.0-20200714014557-cc97c2a69cc2 h1:2+XIy4DzLymP/X7LFH03WBdHpkVavb0MC0fQrPvRGxs=
github.com/diamondburned/cchat-discord v0.0.0-20200714014557-cc97c2a69cc2/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
github.com/diamondburned/cchat-discord v0.0.0-20200714063838-0a590627268b h1:dZ7fntEpmeh2xvwZ6jqyWjzk9Ikvx0o/O7pEDMR1PzU=
github.com/diamondburned/cchat-discord v0.0.0-20200714063838-0a590627268b/go.mod h1:KRdCNnJHsHIcQP6/fE90MKTIZJuGyTKW/aTx18eGuuI=
github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3 h1:xr07/2cwINyrMqh92pQQJVDfQqG0u6gHAK+ZcGfpSew=
github.com/diamondburned/cchat-mock v0.0.0-20200704044009-f587c4904aa3/go.mod h1:SRu3OOeggELFr2Wd3/+SpYV1eNcvSk2LBhM70NOZSG8=
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4 h1:aealHwitg0XO8Nfgitq73FRsNCETeKX7M9Rqnwm32No=
github.com/diamondburned/cchat-discord v0.0.0-20200715034853-d1e516c919c4/go.mod h1:ZmeZUjT3TVEItYUTi274ICTi+xDf2CCimD2yXRCaWdo=
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b h1:sq0MXjJc3yAOZvuolRxOpKQNvpMLyTmsECxQqdYgF5E=
github.com/diamondburned/cchat-mock v0.0.0-20200709231652-ad222ce5a74b/go.mod h1:+bAf0m2o5qH54DmYJ/lR1HeITV53ol0JaoKyFFx3m3E=
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d h1:Ha/I6PMKi+B4hpWclwlXj0tUMehR7Q0TNxPczzBwzPI=
github.com/diamondburned/gotk3 v0.0.0-20200630065217-97aeb06d705d/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/diamondburned/imgutil v0.0.0-20200704034004-40dbfc732516 h1:6j4oZahbNdVhSEInRfeYbgDpx1FXDfJy6CcUVyWOuVY=
github.com/diamondburned/imgutil v0.0.0-20200704034004-40dbfc732516/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
github.com/diamondburned/imgutil v0.0.0-20200708012333-53c9e45dd28b h1:iYKHGvWzNFBIRTSY8Pd5g301YDGWMfs3fh1VS0iBSj0=
github.com/diamondburned/imgutil v0.0.0-20200708012333-53c9e45dd28b/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972 h1:OWxllHbUptXzDias6YI4MM0R3o50q8MfhkkwVIlfiNo=
github.com/diamondburned/imgutil v0.0.0-20200710174014-8a3be144a972/go.mod h1:kBQKaukR/LyCfhED99/T4/XxUMDNEEzf1Fx6vreD3RQ=
github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249 h1:yP7kJ+xCGpDz6XbcfACJcju4SH1XDPwlrvbofz3lP8I=
github.com/diamondburned/ningen v0.1.1-0.20200621014632-6babb812b249/go.mod h1:xW9hpBZsGi8KpAh10TyP+YQlYBo+Xc+2w4TR6N0951A=
github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851 h1:xf1aLPnwK/Yn2z7dBIgQROSVOEc2wtivgnnwBItdEVM=
github.com/diamondburned/ningen v0.1.1-0.20200708090333-227e90d19851/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg=
github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede h1:qRmfQCOS+ZnH4G0+8O09PUx3HQTdQwzsDoo1ucTgm2E=
github.com/diamondburned/ningen v0.1.1-0.20200708211706-57c712372ede/go.mod h1:FNezDLQIhoDS+RkXLSQ7dJNrt6BW/nVl1krzDgWMQwg=
github.com/diamondburned/ningen v0.1.1-0.20200711215126-d4b8a17e818d h1:XgG/KRbAwu8v2/YZWimjXo0dgdD69E38ollCfbFtU7s=
github.com/diamondburned/ningen v0.1.1-0.20200711215126-d4b8a17e818d/go.mod h1:NVneOJDUDEIC3cnyeh2vpeAPVtBdC2Kcy+uwDy4o2qk=
github.com/diamondburned/ningen v0.1.1-0.20200712031630-349ee2c3f01c h1:CpYhIGiRzee7Jm0H4c0fLvRe/08QitDNo8KHYtrOmFE=
github.com/diamondburned/ningen v0.1.1-0.20200712031630-349ee2c3f01c/go.mod h1:NVneOJDUDEIC3cnyeh2vpeAPVtBdC2Kcy+uwDy4o2qk=
github.com/diamondburned/ningen v0.1.1-0.20200715034121-61de7138eb56 h1:DAk3bEwJZycjfZu4OXDYrR/nmpy3ZS/dfUF0rskfVj0=
github.com/diamondburned/ningen v0.1.1-0.20200715034121-61de7138eb56/go.mod h1:SKPY3387RHCbMrnefex9D+zlrA2yB+LCtaaQAgatAuc=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=

15
icons/byte Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Script to embed a file to Go.
for file in "$@"; {
name="${file%.*}"
name="${name//[^[:alnum:]]/_}" # sanitize
file2byteslice \
-input "$file" \
-output "${name}.go" \
-package icons \
-var "__${name}"
echo "Written $file to \`var __$name'"
}

6
icons/cchat_256.go Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,22 +9,23 @@ import (
)
// static assets
var logo256 *gdk.Pixbuf
var assets = map[string]*gdk.Pixbuf{}
func Logo256() *gdk.Pixbuf {
if logo256 == nil {
logo256 = loadPixbuf(pkger.Include("/icons/cchat-variant2_256.png"))
}
return logo256
func Logo256Variant2() *gdk.Pixbuf {
return loadPixbuf(__cchat_variant2_256)
}
func loadPixbuf(name string) *gdk.Pixbuf {
func Logo256() *gdk.Pixbuf {
return loadPixbuf(__cchat_256)
}
func loadPixbuf(data []byte) *gdk.Pixbuf {
l, err := gdk.PixbufLoaderNew()
if err != nil {
log.Fatalln("Failed to create a pixbuf loader for icons:", err)
}
p, err := l.WriteAndReturnPixbuf(readFile(name))
p, err := l.WriteAndReturnPixbuf(data)
if err != nil {
log.Fatalln("Failed to write and return pixbuf:", err)
}

View File

@ -29,24 +29,14 @@ func loadProviders(screen *gdk.Screen) {
}
}
func LoadCSS(files ...string) {
var buf bytes.Buffer
for _, file := range files {
buf.Reset()
if err := readFile(&buf, file); err != nil {
log.Error(errors.Wrap(err, "Failed to load a CSS file"))
continue
}
prov, _ := gtk.CssProviderNew()
if err := prov.LoadFromData(buf.String()); err != nil {
log.Error(errors.Wrap(err, "Failed to parse CSS "+file))
continue
}
cssRepos[file] = prov
func LoadCSS(name, css string) {
prov, _ := gtk.CssProviderNew()
if err := prov.LoadFromData(css); err != nil {
log.Error(errors.Wrap(err, "Failed to parse CSS in "+name))
return
}
cssRepos[name] = prov
}
func readFile(buf *bytes.Buffer, file string) error {

View File

@ -23,6 +23,8 @@ var App struct {
*gtk.Application
Window *gtk.ApplicationWindow
Header *gtk.HeaderBar
Throttler *throttler.State
}
// Clipboard is initialized on init().
@ -37,6 +39,7 @@ func NewModalDialog() (*gtk.Dialog, error) {
}
d.SetModal(true)
d.SetTransientFor(App.Window)
App.Throttler.Connect(d)
return d, nil
}
@ -63,48 +66,48 @@ func AddAppAction(name string, call func()) {
App.AddAction(action)
}
// Commented because this is not a good function to use. Components should use
// AddAppAction instead.
// func AddWindowAction(name string, call func()) {
// action := glib.SimpleActionNew(name, nil)
// action.Connect("activate", call)
// App.Window.AddAction(action)
// }
func init() {
gtk.Init(&Args)
App.Application, _ = gtk.ApplicationNew(AppID, 0)
Clipboard, _ = gtk.ClipboardGet(gdk.SELECTION_CLIPBOARD)
// Limit the TPS of the main loop on window unfocus.
App.Throttler = throttler.Bind(App.Application)
}
type WindowHeaderer interface {
type Window interface {
Window() gtk.IWidget
Header() gtk.IWidget
Menu() *glib.MenuModel
Icon() *gdk.Pixbuf
Close()
}
func Main(wfn func() WindowHeaderer) {
func Main(wfn func() Window) {
App.Application.Connect("activate", func() {
// Load all CSS onto the default screen.
loadProviders(getDefaultScreen())
// Execute the function later, because we need it to run after
// initialization.
w := wfn()
App.Application.SetAppMenu(w.Menu())
App.Header, _ = gtk.HeaderBarNew()
// Right buttons only.
App.Header.SetDecorationLayout("menu:minimize,close")
App.Header.SetShowCloseButton(true)
App.Header.Show()
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
App.Header.SetCustomTitle(b)
// b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
// App.Header.SetCustomTitle(b)
App.Window, _ = gtk.ApplicationWindowNew(App.Application)
App.Window.SetDefaultSize(1000, 500)
App.Window.SetTitlebar(App.Header)
App.Window.SetIcon(w.Icon())
App.Window.Show()
// Execute the function later, because we need it to run after
// initialization.
w := wfn()
App.Window.Add(w.Window())
App.Header.Add(w.Header())
@ -127,8 +130,6 @@ func Main(wfn func() WindowHeaderer) {
})
})
// Limit the TPS of the main loop on unfocus.
throttler.Bind(App.Window)
})
// Use a special function to run the application. Exit with the appropriate
@ -223,6 +224,7 @@ func SpawnUploader(dirpath string, callback func(absolutePaths []string)) {
"Upload", gtk.RESPONSE_ACCEPT,
)
App.Throttler.Connect(dialog)
BindPreviewer(dialog)
if dirpath == "" {

View File

@ -20,19 +20,25 @@ type Connector interface {
Connect(string, interface{}, ...interface{}) (glib.SignalHandle, error)
}
func Bind(evc Connector) *State {
func Bind(app *gtk.Application) *State {
var settings, _ = gtk.SettingsGetDefault()
var s = State{
settings: settings,
ticker: time.Tick(time.Second / TPS),
}
evc.Connect("focus-out-event", s.Start)
evc.Connect("focus-in-event", s.Stop)
app.Connect("window-added", func(app *gtk.Application, w *gtk.Window) {
s.Connect(w)
})
return &s
}
func (s *State) Connect(c Connector) {
c.Connect("focus-out-event", s.Start)
c.Connect("focus-in-event", s.Stop)
}
func (s *State) Start() {
if s.throttling {
return

View File

@ -4,14 +4,15 @@ import (
"html"
"strings"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/actions"
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
"github.com/gotk3/gotk3/gtk"
)
type header struct {
*gtk.Box
left *headerLeft // TODO
left *headerLeft // middle-ish
right *headerRight
}
@ -41,26 +42,33 @@ func newHeader() *header {
const BreadcrumbSlash = `<span weight="light" rise="-1024" size="x-large">/</span>`
func (h *header) SetBreadcrumb(b breadcrumb.Breadcrumb) {
for i := range b {
b[i] = html.EscapeString(b[i])
func (h *header) SetBreadcrumber(b breadcrumb.Breadcrumber) {
if b == nil {
h.right.breadcrumb.SetText("")
return
}
var crumb = b.Breadcrumb()
for i := range crumb {
crumb[i] = html.EscapeString(crumb[i])
}
h.right.breadcrumb.SetMarkup(
BreadcrumbSlash + " " + strings.Join(b, " "+BreadcrumbSlash+" "),
BreadcrumbSlash + " " + strings.Join(crumb, " "+BreadcrumbSlash+" "),
)
}
func (h *header) SetSessionMenu(s *session.Row) {
h.left.openmenu.Bind(s.ActionsMenu)
}
type headerLeft struct {
*gtk.Box
openmenu *gtk.MenuButton
openmenu *actions.MenuButton
}
func newHeaderLeft() *headerLeft {
openmenu := primitives.NewMenuActionButton([][2]string{
{"Preferences", "app.preferences"},
{"Quit", "app.quit"},
})
openmenu := actions.NewMenuButton()
openmenu.Show()
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)

View File

@ -5,7 +5,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/input"
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/gotk3/gotk3/gtk"
)

View File

@ -11,7 +11,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/messages/message"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/roundimage"
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/gotk3/gotk3/gtk"
)

View File

@ -8,7 +8,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat-gtk/internal/ui/rich/labeluri"
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/diamondburned/cchat/text"
"github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/pango"

View File

@ -16,7 +16,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/messages/typing"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/autoscroll"
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors"
)
@ -85,7 +85,7 @@ func NewView() *View {
primitives.AddClass(view.Box, "message-view")
// placeholder logo
logo, _ := gtk.ImageNewFromPixbuf(icons.Logo256())
logo, _ := gtk.ImageNewFromPixbuf(icons.Logo256Variant2())
logo.Show()
view.FaceView = sadface.New(view.Box, logo)

View File

@ -0,0 +1,75 @@
package actions
import (
"fmt"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
)
type ActionGroupInserter interface {
InsertActionGroup(prefix string, action glib.IActionGroup)
}
var _ ActionGroupInserter = (*gtk.Widget)(nil)
type Menu struct {
*Stateful
menu *glib.Menu
prefix string
}
func NewMenu(prefix string) *Menu {
return &Menu{
Stateful: NewStateful(), // actiongroup and menu not linked
menu: glib.MenuNew(),
prefix: prefix,
}
}
func (m *Menu) Prefix() string {
return m.prefix
}
func (m *Menu) MenuModel() (string, *glib.MenuModel) {
return m.prefix, &m.menu.MenuModel
}
func (m *Menu) InsertActionGroup(w ActionGroupInserter) {
w.InsertActionGroup(m.prefix, m)
}
func (m *Menu) Popover(relative gtk.IWidget) *gtk.Popover {
_, model := m.MenuModel()
p, _ := gtk.PopoverNewFromModel(relative, model)
p.SetPosition(gtk.POS_RIGHT)
return p
}
func (m *Menu) Reset() {
m.menu.RemoveAll()
m.Stateful.Reset()
}
func (m *Menu) AddAction(label string, call func()) {
m.Stateful.AddAction(label, call)
m.menu.Append(label, fmt.Sprintf("%s.%s", m.prefix, ActionName(label)))
}
func (m *Menu) RemoveAction(label string) {
var labels = m.Stateful.labels
for i, l := range labels {
if l == label {
labels = append(labels[:i], labels[:i+1]...)
m.menu.Remove(i)
m.Stateful.labels = labels
m.Stateful.group.RemoveAction(ActionName(label))
return
}
}
}

View File

@ -0,0 +1,55 @@
package actions
import (
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
)
type MenuButton struct {
*gtk.MenuButton
lastsig glib.SignalHandle
lastmod *glib.MenuModel
}
func NewMenuButton() *MenuButton {
b, _ := gtk.MenuButtonNew()
b.SetSensitive(false)
return &MenuButton{
MenuButton: b,
}
}
// Bind binds the given menu. The menu's prefix MUST be a constant for this
// instance of the MenuButton.
func (m *MenuButton) Bind(menu *Menu) {
prefix, model := menu.MenuModel()
// Insert the action group into the menu. This will only override the old
// action group, as the prefix is a constant for this instance.
m.MenuButton.InsertActionGroup(prefix, menu)
// Only after we have inserted the action group can we set the model that
// menu has. This tells Gtk to look for the menu actions inside the inserted
// group.
m.MenuButton.SetMenuModel(model)
// Unbind the last handler if we have one.
if m.lastmod != nil {
m.lastmod.HandlerDisconnect(m.lastsig)
}
// Set the current model as the last one for future calls.
if m.lastmod = model; m.lastmod != nil {
// If we have a model, then only activate the button when we have any
// menu items.
m.SetSensitive(model.GetNItems() > 0)
// Subscribe the button to menu update events.
m.lastsig, _ = model.Connect("items-changed", func() {
m.SetSensitive(model.GetNItems() > 0)
})
} else {
// Else, don't allow the button to be clicked at all.
m.SetSensitive(false)
}
}

View File

@ -0,0 +1,69 @@
package actions
import (
"log"
"strings"
"github.com/gotk3/gotk3/glib"
)
// Stateful is a stateful action group, which would allow additional methods
// that would otherwise be impossible to do with a simple Action Map.
type Stateful struct {
glib.IActionGroup
group *glib.SimpleActionGroup
labels []string // labels
}
func NewStateful() *Stateful {
group := glib.SimpleActionGroupNew()
return &Stateful{
IActionGroup: group,
group: group,
}
}
func (s *Stateful) Reset() {
for _, label := range s.labels {
s.group.RemoveAction(ActionName(label))
}
s.labels = nil
}
func (s *Stateful) AddAction(label string, call func()) {
sa := glib.SimpleActionNew(ActionName(label), nil)
sa.Connect("activate", call)
s.labels = append(s.labels, label)
s.group.AddAction(sa)
}
func (s *Stateful) LookupAction(label string) *glib.Action {
for _, l := range s.labels {
if l == label {
return s.group.LookupAction(ActionName(label))
}
}
return nil
}
func (s *Stateful) RemoveAction(label string) {
for i, l := range s.labels {
if l == label {
s.labels = append(s.labels[:i], s.labels[:i+1]...)
s.group.RemoveAction(ActionName(label))
return
}
}
}
// ActionName converts the label name into the action name.
func ActionName(label string) (actionName string) {
actionName = strings.Replace(label, " ", "-", -1)
if !glib.ActionNameIsValid(actionName) {
log.Panicf("Label makes for invalid action name %q\n", actionName)
}
return
}

View File

@ -20,6 +20,13 @@ func NewLazyMenu(bindTo primitives.Connector) *LazyMenu {
return l
}
func (m *LazyMenu) popup(w gtk.IWidget, ev *gdk.Event) {
// Is this a right click? Run the menu if yes.
if gts.EventIsRightClick(ev) {
m.PopupAtPointer(ev)
}
}
func (m *LazyMenu) SetItems(items []Item) {
m.items = items
}
@ -47,13 +54,6 @@ func (m *LazyMenu) PopupAtPointer(ev *gdk.Event) {
menu.PopupAtPointer(ev)
}
func (m *LazyMenu) popup(w gtk.IWidget, ev *gdk.Event) {
// Is this a right click? Run the menu if yes.
if gts.EventIsRightClick(ev) {
m.PopupAtPointer(ev)
}
}
type MenuAppender interface {
Append(gtk.IMenuItem)
}
@ -92,6 +92,7 @@ func ToolbarItems(toolbar ToolbarInserter, items []Item) {
}
type Item struct {
Icon string
Name string
Func func()
Extra func(*gtk.MenuItem)
@ -101,6 +102,19 @@ func SimpleItem(name string, fn func()) Item {
return Item{Name: name, Func: fn}
}
// func (item Item) ToModelButton() *gtk.ModelButton {
// b, _ := gtk.ModelButtonNew()
// b.SetLabel(action[0])
// b.SetActionName(action[1])
// b.Show()
// // Set the label's alignment in a hacky way.
// c, _ := b.GetChild()
// l := c.(LabelTweaker)
// l.SetUseMarkup(true)
// l.SetHAlign(gtk.ALIGN_START)
// }
func (item Item) ToMenuItem() *gtk.MenuItem {
mb, _ := gtk.MenuItemNewWithLabel(item.Name)
mb.Connect("activate", item.Func)

View File

@ -40,7 +40,6 @@ func NewIcon(sizepx int, procs ...imgutil.Processor) *Icon {
img, _ := roundimage.NewImage(0)
img.Show()
img.SetSizeRequest(sizepx, sizepx)
rev, _ := gtk.RevealerNew()
rev.Add(img)
@ -93,7 +92,6 @@ func (i *Icon) CopyPixbuf(dst httputil.ImageContainer) {
func (i *Icon) SetPlaceholderIcon(iconName string, iconSzPx int) {
i.Image.SetRadius(-1) // square
i.SetRevealChild(true)
i.SetSize(iconSzPx)
if iconName != "" {
primitives.SetImageIcon(i.Image.Image, iconName, iconSzPx)
@ -136,6 +134,24 @@ func (i *Icon) updateAsync() {
httputil.AsyncImageSized(i.Image, i.url, i.size, i.size, i.procs...)
}
type EventIcon struct {
*gtk.EventBox
Icon *Icon
}
func NewEventIcon(sizepx int, pp ...imgutil.Processor) *EventIcon {
icn := NewIcon(sizepx, pp...)
icn.Show()
evb, _ := gtk.EventBoxNew()
evb.Add(icn)
return &EventIcon{
EventBox: evb,
Icon: icn,
}
}
type ToggleButtonImage struct {
gtk.ToggleButton
Labeler

View File

@ -4,7 +4,7 @@ import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/diamondburned/cchat/text"
)

View File

@ -3,7 +3,7 @@ package config
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/gotk3/gotk3/gtk"
)

View File

@ -12,6 +12,8 @@ type ViewController interface {
RowSelected(*session.Row, *server.ServerRow, cchat.ServerMessage)
SessionSelected(*Service, *session.Row)
AuthenticateSession(*List, *Service)
OnSessionRemove(*Service, *session.Row)
OnSessionDisconnect(*Service, *session.Row)
}
// List is a list of services. Each service is a revealer that contains
@ -59,6 +61,14 @@ func (sl *List) AuthenticateSession(svc *Service) {
sl.ViewController.AuthenticateSession(sl, svc)
}
func (sl *List) OnSessionRemove(svc *Service, row *session.Row) {
sl.ViewController.OnSessionRemove(svc, row)
}
func (sl *List) OnSessionDisconnect(svc *Service, row *session.Row) {
sl.ViewController.OnSessionDisconnect(svc, row)
}
func (sl *List) AddService(svc cchat.Service) {
row := NewService(svc, sl)
row.Show()

View File

@ -24,6 +24,9 @@ type ListController interface {
SessionSelected(*Service, *session.Row)
// AuthenticateSession tells View to call to the parent's authenticator.
AuthenticateSession(*Service)
OnSessionRemove(*Service, *session.Row)
OnSessionDisconnect(*Service, *session.Row)
}
// Service holds everything that a single service has.
@ -165,8 +168,16 @@ func (s *Service) Service() cchat.Service {
}
func (s *Service) OnSessionDisconnect(row *session.Row) {
s.BodyList.RemoveSessionRow(row.Session.ID())
s.SaveAllSessions()
// Unselect if selected.
if cur := s.BodyList.GetSelectedRow(); cur.GetIndex() == row.GetIndex() {
s.BodyList.UnselectAll()
}
s.svclctrl.OnSessionDisconnect(s, row)
// WHY WAS THIS HERE?!?!?!
// s.BodyList.RemoveSessionRow(row.Session.ID())
// s.SaveAllSessions()
}
func (s *Service) RowSelected(r *session.Row, sv *server.ServerRow, m cchat.ServerMessage) {
@ -174,6 +185,7 @@ func (s *Service) RowSelected(r *session.Row, sv *server.ServerRow, m cchat.Serv
}
func (s *Service) RemoveSession(row *session.Row) {
s.svclctrl.OnSessionRemove(s, row)
s.BodyList.RemoveSessionRow(row.Session.ID())
s.SaveAllSessions()
}

View File

@ -5,23 +5,6 @@ import (
"github.com/gotk3/gotk3/gtk"
)
// TODO rename file
// TODO: round buttons
func NewAddButton() *gtk.ListBoxRow {
img, _ := gtk.ImageNew()
img.Show()
primitives.SetImageIcon(img, "list-add-symbolic", IconSize/2)
row, _ := gtk.ListBoxRowNew()
row.SetSizeRequest(IconSize, IconSize)
row.SetSelectable(false) // activatable though
row.Add(img)
row.Show()
return row
}
type ServiceController interface {
SessionSelected(*Row)
AuthenticateSession()

View File

@ -7,7 +7,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
"github.com/diamondburned/cchat-gtk/internal/ui/service/button"
"github.com/diamondburned/cchat-gtk/internal/ui/service/menu"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/menu"
"github.com/diamondburned/cchat/text"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors"

View File

@ -6,6 +6,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/keyring"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/actions"
"github.com/diamondburned/cchat-gtk/internal/ui/primitives/spinner"
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser/markup"
@ -13,6 +14,7 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/commander"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
"github.com/diamondburned/cchat/text"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
"github.com/pkg/errors"
)
@ -47,7 +49,9 @@ type Servicer interface {
// Row represents a session row entry in the session List.
type Row struct {
*gtk.ListBoxRow
icon *rich.Icon // nilable
icon *rich.EventIcon // nilable
parentcrumb breadcrumb.Breadcrumber
Session cchat.Session // state; nilable
sessionID string
@ -55,6 +59,8 @@ type Row struct {
Servers *Servers // accessed by View for the right view
svcctrl Servicer
ActionsMenu *actions.Menu // session.*
// TODO: enum class? having the button be red on fail would be good
// put commander in either a hover menu or a right click menu. maybe in the
@ -78,6 +84,9 @@ var rowIconCSS = primitives.PrepareClassCSS("session-icon", `
padding: 4px;
margin: 0;
}
.session-icon.failed {
background-color: alpha(red, 0.45);
}
`)
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Servicer) *Row {
@ -94,34 +103,74 @@ func NewLoading(parent breadcrumb.Breadcrumber, id, name string, ctrl Servicer)
}
func newRow(parent breadcrumb.Breadcrumber, name text.Rich, ctrl Servicer) *Row {
row := &Row{svcctrl: ctrl}
row := &Row{
svcctrl: ctrl,
parentcrumb: parent,
}
row.icon = rich.NewIcon(IconSize)
row.icon.SetPlaceholderIcon(IconName, IconSize)
row.icon = rich.NewEventIcon(IconSize)
row.icon.Icon.SetPlaceholderIcon(IconName, IconSize)
row.icon.Show()
rowIconCSS(row.icon)
rowIconCSS(row.icon.Icon)
row.ListBoxRow, _ = gtk.ListBoxRowNew()
rowCSS(row.ListBoxRow)
// TODO: commander button
row.Servers = NewServers(parent, row)
row.Servers = NewServers(row, row)
row.Servers.Show()
// Bind session.* actions into row.
row.ActionsMenu = actions.NewMenu("session")
row.ActionsMenu.InsertActionGroup(row)
// Bind right clicks and show a popover menu on such event.
row.icon.Connect("button-press-event", func(_ gtk.IWidget, ev *gdk.Event) {
if gts.EventIsRightClick(ev) {
row.ActionsMenu.Popover(row).Popup()
}
})
return row
}
func NewAddButton() *gtk.ListBoxRow {
img, _ := gtk.ImageNew()
img.Show()
primitives.SetImageIcon(img, "list-add-symbolic", IconSize/2)
row, _ := gtk.ListBoxRowNew()
row.SetSizeRequest(IconSize, IconSize)
row.SetSelectable(false) // activatable though
row.Add(img)
row.Show()
rowCSS(row)
return row
}
// Reset extends the server row's Reset function and resets additional states.
// It resets all states back to nil, but the session ID stays.
func (r *Row) Reset() {
r.Servers.Reset() // wipe servers
// TODO: better visual clue
r.icon.Image.SetFromPixbuf(nil) // wipe image
r.Servers.Reset() // wipe servers
r.ActionsMenu.Reset() // wipe menu items
// Set a lame placeholder icon.
r.icon.Icon.SetPlaceholderIcon("folder-remote-symbolic", IconSize)
r.Session = nil
r.cmder = nil
}
func (r *Row) SessionID() string {
return r.sessionID
}
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
return breadcrumb.Try(r.parentcrumb, r.Session.Name().Content)
}
// Activate executes whatever needs to be done. If the row has failed, then this
// method will reconnect. If the row is already loaded, then SessionSelected
// will be called.
@ -142,11 +191,14 @@ func (r *Row) SetLoading() {
r.Session = nil
// Reset the icon.
r.icon.Image.SetFromPixbuf(nil)
r.icon.Icon.Reset()
// Remove everything from the row, including the icon.
primitives.RemoveChildren(r)
// Remove the failed class.
primitives.RemoveClass(r.icon.Icon, "failed")
// Add a loading circle.
spin := spinner.New()
spin.SetSizeRequest(IconSize, IconSize)
@ -170,7 +222,9 @@ func (r *Row) SetFailed(err error) {
// Add the icon.
r.Add(r.icon)
// Set the button to a retry icon.
r.icon.SetPlaceholderIcon("view-refresh-symbolic", IconSize)
r.icon.Icon.SetPlaceholderIcon("view-refresh-symbolic", IconSize)
// Mark the icon as failed.
primitives.AddClass(r.icon.Icon, "failed")
// SetFailed, but also add the callback to retry.
// r.Row.SetFailed(err, r.ReconnectSession)
@ -196,11 +250,11 @@ func (r *Row) SetSession(ses cchat.Session) {
r.Session = ses
r.sessionID = ses.ID()
r.SetTooltipMarkup(markup.Render(ses.Name()))
r.icon.SetPlaceholderIcon(IconName, IconSize)
r.icon.Icon.SetPlaceholderIcon(IconName, IconSize)
// If the session has an icon, then use it.
if iconer, ok := ses.(cchat.Icon); ok {
r.icon.AsyncSetIconer(iconer, "Failed to set session icon")
r.icon.Icon.AsyncSetIconer(iconer, "Failed to set session icon")
}
// Update to indicate that we're done.
@ -208,20 +262,22 @@ func (r *Row) SetSession(ses cchat.Session) {
r.SetSensitive(true)
r.Add(r.icon)
// Bind extra menu items before loading. These items won't be clickable
// during loading.
r.ActionsMenu.Reset()
r.ActionsMenu.AddAction("Disconnect", r.DisconnectSession)
r.ActionsMenu.AddAction("Remove", r.RemoveSession)
// Set the commander, if any. The function will return nil if the assertion
// returns nil. As such, we assert with an ignored ok bool, allowing cmd to
// be nil.
cmd, _ := ses.(commander.SessionCommander)
r.cmder = commander.NewBuffer(r.svcctrl.Service(), cmd)
// TODO commander button
// // Bind extra menu items before loading. These items won't be clickable
// // during loading.
// r.SetNormalExtraMenu([]menu.Item{
// menu.SimpleItem("Disconnect", r.DisconnectSession),
// menu.SimpleItem("Remove", r.RemoveSession),
// })
// Show the command button if the session actually supports the commander.
if r.cmder != nil {
r.ActionsMenu.AddAction("Command Prompt", r.ShowCommander)
}
// Load all top-level servers now.
r.Servers.SetList(ses)
@ -277,6 +333,9 @@ func (r *Row) DisconnectSession() {
// Call the disconnect function from the controller first.
r.svcctrl.OnSessionDisconnect(r)
// Copy the session to avoid data race and allow us to reset.
session := r.Session
// Show visually that we're disconnected first by wiping all servers.
r.Reset()
@ -287,26 +346,18 @@ func (r *Row) DisconnectSession() {
// Try and disconnect asynchronously.
gts.Async(func() (func(), error) {
// Disconnect and wrap the error if any. Wrap works with a nil error.
err := errors.Wrap(r.Session.Disconnect(), "Failed to disconnect.")
err := errors.Wrap(session.Disconnect(), "Failed to disconnect.")
return func() {
// Re-enable access to the menu.
r.SetSensitive(true)
// Set the menu to allow disconnection.
// r.Button.SetNormalExtraMenu([]menu.Item{
// menu.SimpleItem("Connect", r.ReconnectSession),
// menu.SimpleItem("Remove", r.RemoveSession),
// })
r.ActionsMenu.AddAction("Connect", r.ReconnectSession)
r.ActionsMenu.AddAction("Remove", r.RemoveSession)
}, err
})
}
// // KeyringSession returns a keyring session, or nil if the session cannot be
// // saved.
// func (r *Row) KeyringSession() *keyring.Session {
// return keyring.ConvertSession(r.Session)
// }
// ID returns the session ID.
func (r *Row) ID() string {
return r.sessionID

View File

@ -33,14 +33,11 @@ type Controller interface {
AuthenticateSession(*List, *Service)
// OnSessionRemove is called to remove a session. This should also clear out
// the message view in the parent package.
OnSessionRemove(id string)
OnSessionRemove(*Service, *session.Row)
// OnSessionDisconnect is here to satisfy session's controller.
OnSessionDisconnect(id string)
OnSessionDisconnect(*Service, *session.Row)
}
// LeftWidth is the width of the left-most services panel.
const LeftWidth = IconSize
type View struct {
*gtk.Box // 2 panes, but left-most hard-coded
Controller // inherit main controller
@ -57,7 +54,6 @@ func NewView(ctrller Controller) *View {
view := &View{Controller: ctrller}
view.Services = NewList(view)
view.Services.SetSizeRequest(LeftWidth, -1)
view.Services.Show()
// Make a separator.

View File

@ -1,7 +0,0 @@
/* Make CSS more consistent across themes */
headerbar { padding: 0; }
popover > *:not(stack):not(button) { margin: 6px; }
/* Hack to fix the input bar being high in Adwaita */
.input-field * { min-height: 0; }

View File

@ -2,6 +2,7 @@ package ui
import (
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-gtk/icons"
"github.com/diamondburned/cchat-gtk/internal/gts"
"github.com/diamondburned/cchat-gtk/internal/log"
"github.com/diamondburned/cchat-gtk/internal/ui/config/preferences"
@ -10,14 +11,25 @@ import (
"github.com/diamondburned/cchat-gtk/internal/ui/service/auth"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"github.com/markbates/pkger"
"github.com/pkg/errors"
)
func init() {
// Load the local CSS.
gts.LoadCSS(pkger.Include("/internal/ui/style.css"))
gts.LoadCSS("main", `
/* Make CSS more consistent across themes */
headerbar { padding-left: 0 }
.appmenu { margin: 0 18px }
popover > *:not(stack):not(button) { margin: 6px }
/* Hack to fix the input bar being high in Adwaita */
.input-field * { min-height: 0 }
`)
}
// constraints for the left panel
@ -47,7 +59,7 @@ type App struct {
}
var (
_ gts.WindowHeaderer = (*App)(nil)
_ gts.Window = (*App)(nil)
_ service.Controller = (*App)(nil)
)
@ -76,24 +88,25 @@ func (app *App) AddService(svc cchat.Service) {
}
// OnSessionRemove resets things before the session is removed.
func (app *App) OnSessionRemove(id string) {
func (app *App) OnSessionRemove(s *service.Service, r *session.Row) {
// Reset the message view if it's what we're showing.
if app.window.MessageView.SessionID() == id {
if app.window.MessageView.SessionID() == r.SessionID() {
app.window.MessageView.Reset()
app.header.SetBreadcrumb(nil)
app.header.SetBreadcrumber(nil)
}
}
func (app *App) OnSessionDisconnect(id string) {
func (app *App) OnSessionDisconnect(s *service.Service, r *session.Row) {
// We're basically doing the same thing as removing a session. Check
// OnSessionRemove above.
app.OnSessionRemove(id)
app.OnSessionRemove(s, r)
}
func (app *App) SessionSelected(svc *service.Service, ses *session.Row) {
// TODO: restore last message box
app.window.MessageView.Reset()
app.header.SetBreadcrumb(nil)
app.header.SetBreadcrumber(ses)
app.header.SetSessionMenu(ses)
}
func (app *App) RowSelected(ses *session.Row, srv *server.ServerRow, smsg cchat.ServerMessage) {
@ -106,7 +119,7 @@ func (app *App) RowSelected(ses *session.Row, srv *server.ServerRow, smsg cchat.
app.lastSelector = srv.SetSelected
app.lastSelector(true)
app.header.SetBreadcrumb(srv.Breadcrumb())
app.header.SetBreadcrumber(srv)
// Disable the server list because we don't want the user to switch around
// while we're loading.
@ -155,3 +168,15 @@ func (app *App) Header() gtk.IWidget {
func (app *App) Window() gtk.IWidget {
return app.window
}
func (app *App) Icon() *gdk.Pixbuf {
return icons.Logo256()
}
func (app *App) Menu() *glib.MenuModel {
menu := glib.MenuNew()
menu.Append("Preferences", "app.preferences")
menu.Append("Quit", "app.quit")
return &menu.MenuModel
}

View File

@ -16,7 +16,7 @@ import (
var destructor = func() {}
func main() {
gts.Main(func() gts.WindowHeaderer {
gts.Main(func() gts.Window {
var app = ui.NewApplication()
// Load all cchat services.

File diff suppressed because one or more lines are too long