Files
clr-installer/gui/pages/bundle.go
T
Karthik Prabhu Vinod c964c76fc1 GUI Net: Add user bundles net-test Note
Fixes Issue: #635

The user used to mistakenly click on items in the additonal bundles
page having to wait long duration unecessarily if the network did not work.
This provides the user with a Note saying that selecting an additional
bundle requires a working internet connection

Signed-off-by: Karthik Prabhu Vinod <karthik.prabhu.vinod@intel.com>
2020-02-11 11:20:55 -08:00

338 lines
8.5 KiB
Go

// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only
package pages
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/gtk"
"github.com/clearlinux/clr-installer/controller"
"github.com/clearlinux/clr-installer/gui/common"
"github.com/clearlinux/clr-installer/gui/network"
"github.com/clearlinux/clr-installer/model"
"github.com/clearlinux/clr-installer/swupd"
"github.com/clearlinux/clr-installer/utils"
)
const (
// IconDirectory is where we can find bundle icons
IconDirectory = "/usr/share/clear/bundle-icons"
)
var (
// IconSuffixes is the supported set of suffixes for the
// current Clear Bundles
IconSuffixes = []string{
".svg",
".png",
}
)
// Bundle is a simple page to help with Bundle settings
type Bundle struct {
model *model.SystemInstall
windowController Controller
bundles []*swupd.Bundle // Known bundles
box *gtk.Box // Main layout
checks *gtk.FlowBox // Where to store checks
scroll *gtk.ScrolledWindow // Scroll the checks
selections []*gtk.CheckButton
clearPage bool
}
type decisionDialog struct {
box *gtk.Box
label *gtk.Label
dialog *gtk.Dialog
confirmButton *gtk.Widget
cancelButton *gtk.Widget
}
// LookupBundleIcon attempts to find the icon for the given bundle.
// If it is found, we'll return true and the icon path, otherwise
// we'll return false with an empty string.
func LookupBundleIcon(bundle *swupd.Bundle) (string, bool) {
for _, suffix := range IconSuffixes {
path := filepath.Join(IconDirectory, fmt.Sprintf("%s%s", bundle.Name, suffix))
if _, err := os.Stat(path); err == nil {
return path, true
}
}
return "", false
}
// createBundleWidget creates new displayable widget for the given bundle
func createBundleWidget(bundle *swupd.Bundle) (*gtk.CheckButton, error) {
// Create the root layout
root, err := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
if err != nil {
return nil, err
}
// Create display check
check, err := gtk.CheckButtonNew()
if err != nil {
return nil, err
}
check.SetMarginTop(6)
check.SetMarginStart(12)
// Create display image
img, err := gtk.ImageNew()
img.SetMarginStart(12)
img.SetMarginEnd(6)
if err != nil {
return nil, err
}
icon, set := LookupBundleIcon(bundle)
if set {
pbuf, err := gdk.PixbufNewFromFileAtSize(icon, 48, 48)
if err != nil {
set = false
} else {
img.SetFromPixbuf(pbuf)
}
}
// Still not set? Fallback.
if !set {
img.SetFromIconName("package-x-generic", gtk.ICON_SIZE_INVALID)
}
img.SetPixelSize(48)
img.SetSizeRequest(48, 48)
root.PackStart(img, false, false, 0)
txt := fmt.Sprintf("<b>%s</b>\n%s", bundle.Name, utils.Locale.Get(bundle.Desc))
label, err := gtk.LabelNew(txt)
if err != nil {
return nil, err
}
label.SetMarginStart(6)
label.SetMarginEnd(12)
label.SetXAlign(0.0)
root.PackStart(label, false, false, 0)
label.SetUseMarkup(true)
check.Add(root)
return check, nil
}
func createDecisionBox(model *model.SystemInstall, bundle *Bundle) (*decisionDialog, error) {
var err error
decisionMaker := &decisionDialog{}
decisionMaker.box, err = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
if err != nil {
return nil, err
}
decisionMaker.box.SetHAlign(gtk.ALIGN_FILL)
decisionMaker.box.SetMarginBottom(common.TopBottomMargin)
text := utils.Locale.Get("This requires a working network connection.\nProceed with a network test?")
decisionMaker.label, err = common.SetLabel(text, "label-warning", 0.0)
if err != nil {
return nil, err
}
decisionMaker.label.SetUseMarkup(true)
decisionMaker.label.SetHAlign(gtk.ALIGN_START)
decisionMaker.box.PackStart(decisionMaker.label, false, true, 0)
decisionMaker.dialog, err = common.CreateDialogOkCancel(decisionMaker.box,
utils.Locale.Get("Network Required"), utils.Locale.Get("CONFIRM"), utils.Locale.Get("CANCEL"))
if err != nil {
return nil, err
}
decisionMaker.dialog.SetDeletable(false)
// Configure confirm button
decisionMaker.confirmButton, err = decisionMaker.dialog.GetWidgetForResponse(gtk.RESPONSE_OK)
if err != nil {
return nil, err
}
// Configure cancel button
decisionMaker.cancelButton, err = decisionMaker.dialog.GetWidgetForResponse(gtk.RESPONSE_CANCEL)
if err != nil {
return nil, err
}
_, err = decisionMaker.confirmButton.Connect("clicked", func() {
if ret, _ := network.RunNetworkTest(model); ret != network.NetTestSuccess {
bundle.clearPage = true
bundle.ResetChanges()
decisionMaker.dialog.Destroy()
return
}
bundle.clearPage = false
bundle.windowController.SetButtonState(ButtonConfirm, controller.NetworkPassing)
decisionMaker.dialog.Destroy()
})
if err != nil {
return nil, err
}
_, err = decisionMaker.cancelButton.Connect("clicked", func() {
bundle.clearPage = true
decisionMaker.dialog.Destroy()
bundle.ResetChanges()
})
if err != nil {
return nil, err
}
decisionMaker.confirmButton.SetSensitive(true)
decisionMaker.cancelButton.SetSensitive(true)
decisionMaker.dialog.ShowAll()
return decisionMaker, nil
}
// NewBundlePage returns a new BundlePage
func NewBundlePage(windowController Controller, model *model.SystemInstall) (Page, error) {
var err error
bundle := &Bundle{
windowController: windowController,
model: model,
}
// Load our bundles
bundle.bundles, err = swupd.LoadBundleList(model)
if err != nil {
return nil, err
}
// main layout
bundle.box, err = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
if err != nil {
return nil, err
}
bundle.box.SetBorderWidth(8)
// check list
bundle.checks, err = gtk.FlowBoxNew()
if err != nil {
return nil, err
}
bundle.checks.SetSelectionMode(gtk.SELECTION_MULTIPLE)
bundle.scroll, err = gtk.ScrolledWindowNew(nil, nil)
if err != nil {
return nil, err
}
// no horizontal scrolling
bundle.scroll.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
bundle.scroll.Add(bundle.checks)
bundle.box.PackStart(bundle.scroll, true, true, 0)
// Match the bundle set to our ticks
for _, b := range bundle.bundles {
wid, err := createBundleWidget(b)
if err != nil {
return nil, err
}
bundle.checks.Add(wid)
bundle.selections = append(bundle.selections, wid)
}
for i := range bundle.selections {
if !controller.NetworkPassing {
if _, err := bundle.selections[i].Connect("toggled", func() {
if !controller.NetworkPassing && !bundle.clearPage {
// we dont want to fire any checkbox signals
// when we want to clear the page. if encounter
// clearPage set to true, we set to
// false so that it fire nexttime
// onwards
_, err := createDecisionBox(model, bundle)
if err != nil {
return
}
}
if !controller.NetworkPassing && bundle.clearPage {
bundle.clearPage = false
}
}); err != nil {
return nil, err
}
}
}
return bundle, nil
}
// IsDone checks if all the steps are completed
func (bundle *Bundle) IsDone() bool {
return true
}
// IsRequired will return false as we have default values
func (bundle *Bundle) IsRequired() bool {
return false
}
// GetID returns the ID for this page
func (bundle *Bundle) GetID() int {
return PageIDBundle
}
// GetIcon returns the icon for this page
func (bundle *Bundle) GetIcon() string {
return "applications-system"
}
// GetRootWidget returns the root embeddable widget for this page
func (bundle *Bundle) GetRootWidget() gtk.IWidget {
return bundle.box
}
// GetSummary will return the summary for this page
func (bundle *Bundle) GetSummary() string {
return utils.Locale.Get("Select Additional Bundles")
}
// GetTitle will return the title for this page
func (bundle *Bundle) GetTitle() string {
return bundle.GetSummary()
}
// StoreChanges will store this pages changes into the model
func (bundle *Bundle) StoreChanges() {
// Match model selection to our selections
for n, b := range bundle.bundles {
set := bundle.selections[n].GetActive()
if set {
bundle.model.AddUserBundle(b.Name)
} else {
bundle.model.RemoveUserBundle(b.Name)
}
}
}
// ResetChanges will reset this page to match the model
func (bundle *Bundle) ResetChanges() {
// Match selection to what's in the model
for n, b := range bundle.bundles {
bundle.selections[n].SetActive(bundle.model.ContainsUserBundle(b.Name))
}
bundle.windowController.SetButtonState(ButtonConfirm, controller.NetworkPassing)
}
// GetConfiguredValue returns our current config
func (bundle *Bundle) GetConfiguredValue() string {
if len(bundle.model.UserBundles) == 0 {
return utils.Locale.Get("No additional bundles selected")
}
return " • " + strings.Join(bundle.model.UserBundles, "\n • ")
}