Files
clr-installer/gui/window.go
T
Mark D Horn bd38c8b9d7 Fixes for changes to gotk3 v0.6.0
Several API changes from gotk3 v0.4.0 to v0.6.0
which was a required bump to support building under golang 1.16.x.

Signed-off-by: Mark D Horn <mark.d.horn@intel.com>
2021-07-12 17:05:10 -07:00

1061 lines
30 KiB
Go

// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only
package gui
import (
"sort"
"strings"
"github.com/gotk3/gotk3/gdk"
"github.com/gotk3/gotk3/glib"
"github.com/gotk3/gotk3/gtk"
"github.com/clearlinux/clr-installer/args"
"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/gui/pages"
"github.com/clearlinux/clr-installer/log"
"github.com/clearlinux/clr-installer/model"
"github.com/clearlinux/clr-installer/storage"
"github.com/clearlinux/clr-installer/swupd"
"github.com/clearlinux/clr-installer/syscheck"
"github.com/clearlinux/clr-installer/utils"
)
const (
// WindowWidth specifies the default width of the window
WindowWidth int = 950
// WindowHeight specifies the default height of the window
WindowHeight int = 600
)
// PageConstructor is a typedef of the constructors for our pages
type PageConstructor func(controller pages.Controller, model *model.SystemInstall) (pages.Page, error)
// Window provides management of the underlying GtkWindow and
// associated windows to provide a level of OOP abstraction.
type Window struct {
handle *gtk.Window // Abstract the underlying GtkWindow
mainLayout *gtk.Box // Content layout (horizontal)
banner *Banner // Banner
contentLayout *gtk.Box // Content Layout
rootStack *gtk.Stack // Root-level stack
scroller *gtk.ScrolledWindow
useScroll bool // need to embed content layout to scrolled window
contentContainer *gtk.Container // main container for content
model *model.SystemInstall // model
options args.Args // installer args
rootDir string // root directory
// Menu
menu struct {
switcher *Switcher // Allow switching between main menu
stack *gtk.Stack // Menu switching
screens map[bool]*ContentView // Mapping to content views
welcomePage pages.Page // Pointer to the welcome page
currentPage pages.Page // Pointer to the currently open page
installPage pages.Page // Pointer to the installer page
}
// Buttons
buttons struct {
stack *gtk.Stack // Storage for buttons
boxWelcome *gtk.Box // Storage for welcome buttons
boxPrimary *gtk.Box // Storage for primary buttons
boxSecondary *gtk.Box // Storage for secondary buttons
// Welcome buttons
next *gtk.Button // Launch primary view
exit *gtk.Button // Exit installer
// Primary buttons
install *gtk.Button // Install Clear Linux
quit *gtk.Button // Exit installer
back *gtk.Button // Back to welcome page
// Secondary buttons
confirm *gtk.Button // Confirm changes
cancel *gtk.Button // Cancel changes
}
didInit bool // Whether initialized the view animation
pages map[int]gtk.IWidget // Mapping to each root page
scanInfo pages.ScanInfo // Information related to scanning the media
}
// CreateHeaderBar creates invisible header bar
func (window *Window) CreateHeaderBar() error {
box, err := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
if err != nil {
return err
}
st, err := box.GetStyleContext()
if err != nil {
return err
}
window.handle.SetTitlebar(box)
st.RemoveClass("titlebar")
st.RemoveClass("headerbar")
st.RemoveClass("header")
st.AddClass("invisible-titlebar")
return nil
}
// FitToMonitorSize sets a proper size for the installer if it does
// not fit to the monitor size
func FitToMonitorSize(win *Window, w float32, h float32) error {
screen, err := gdk.ScreenGetDefault()
if err != nil {
return err
}
dpy, err := screen.GetDisplay()
if err != nil {
return err
}
mon, err := dpy.GetPrimaryMonitor()
if err != nil {
return err
}
rect := mon.GetGeometry()
monW := rect.GetWidth()
monH := rect.GetHeight()
// fit to monitor
if WindowWidth > monW || WindowHeight > monH {
relW := int(w * float32(monW))
relH := int(h * float32(monH))
win.handle.SetDefaultSize(relW, relH)
win.useScroll = true
}
return nil
}
// NewWindow creates a new instance of the welcome page
func NewWindow(model *model.SystemInstall, rootDir string, options args.Args) (*Window, error) {
var err error
// Create basic window
window := &Window{
didInit: false,
pages: make(map[int]gtk.IWidget),
model: model,
rootDir: rootDir,
options: options,
}
// Default Icon the application
gtk.WindowSetDefaultIconName("system-software-install")
// Construct main window
window.handle, err = gtk.WindowNew(gtk.WINDOW_TOPLEVEL)
if err != nil {
return nil, err
}
window.handle.SetPosition(gtk.WIN_POS_CENTER)
window.handle.SetDefaultSize(WindowWidth, WindowHeight)
window.handle.SetResizable(false)
window.useScroll = false
err = FitToMonitorSize(window, 0.9, 0.8)
if err != nil {
log.Warning("Could not query monitor size %s", err.Error())
}
// Create invisible header bar
if err = window.CreateHeaderBar(); err != nil {
return nil, err
}
window.mainLayout, err = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
if err != nil {
return nil, err
}
window.handle.Add(window.mainLayout)
// Set locale
utils.SetLocale(model.Language.Code)
// Check if we can boot EFI
if !utils.HostHasEFI() {
log.Warning("Failed to find EFI firmware, falling back to legacy BIOS for installation.")
model.MediaOpts.LegacyBios = true
}
// Create welcome page
window, err = window.createWelcomePage()
if err != nil {
return nil, err
}
// Launch the first page
// If pre-check has been done at least once, start on the menu page
if window.model.PreCheckDone {
window.launchMenuView()
} else {
window.model.PreCheckDone = true
}
window.scanInfo.Channel = make(chan bool)
go func() {
log.Debug("Scanning media")
window.scanInfo.Media, err = storage.RescanBlockDevices(window.model.TargetMedias)
if err != nil {
log.Warning("Error scanning media %s", err.Error())
}
window.scanInfo.Channel <- true
}()
return window, nil
}
// createWelcomePage creates the welcome page
func (window *Window) createWelcomePage() (*Window, error) {
var err error
// Create banner and add to main layout
if window.banner, err = NewBanner(); err != nil {
return nil, err
}
window.mainLayout.PackStart(window.banner.GetRootWidget(), false, false, 0)
// Set up content layout and add to main layout
window.contentLayout, err = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
if err != nil {
return nil, err
}
// default container
window.contentContainer = &window.contentLayout.Container
// useScroll is set, pack default container to a scrolledwindow
if window.useScroll {
window.scroller, err = gtk.ScrolledWindowNew(nil, nil)
if err != nil {
return nil, err
}
window.contentContainer = &window.scroller.Bin.Container
window.scroller.Add(window.contentLayout)
}
window.mainLayout.PackStart(window.contentContainer, true, true, 0)
// Set up the root stack and add to content layout
window.rootStack, err = gtk.StackNew()
window.rootStack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_CROSSFADE)
if err != nil {
return nil, err
}
window.contentLayout.PackStart(window.rootStack, true, true, 0)
// Set up the menu stack and add to root stack
window.menu.stack, err = gtk.StackNew()
if err != nil {
return nil, err
}
window.menu.stack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT)
window.rootStack.AddTitled(window.menu.stack, "menu", "Menu")
// Create the welcome page
pageCreators := []PageConstructor{
pages.NewLanguagePage,
}
for _, f := range pageCreators {
page, err := f(window, window.model)
if err != nil {
return nil, err
}
if err = window.AddPage(page); err != nil {
return nil, err
}
}
// TODO: Remove this temporary code after development phase
_ = window.handle.Connect("destroy", func() { gtk.MainQuit() })
// Create footer area now
if err = window.CreateFooter(); err != nil {
return nil, err
}
window.handle.ShowAll()
window.ActivatePage(window.menu.welcomePage)
// Create syscheck pop-up when system check fails
if syscheckErr := syscheck.RunSystemCheck(true); syscheckErr != nil {
_ = glib.IdleAdd(func() { displaySyscheckDialog(syscheckErr) })
}
return window, nil
}
// createMenuPages creates the menu pages
func (window *Window) createMenuPages() (*Window, error) {
var err error
window.banner.labelText.SetMarkup(GetWelcomeMessage())
window.menu.screens = make(map[bool]*ContentView)
// Set up the stack switcher
window.menu.switcher, err = NewSwitcher(nil)
if err != nil {
return nil, err
}
window.menu.switcher.SetStack(window.menu.stack)
if err = window.InitScreens(); err != nil {
return nil, err
}
window.contentLayout.Remove(window.rootStack)
window.contentLayout.PackStart(window.menu.switcher.GetRootWidget(), false, false, 0)
window.contentLayout.PackStart(window.rootStack, true, true, 0)
// Create footer area
if err = window.UpdateFooter(); err != nil {
return nil, err
}
// Create rest of the pages
pageCreators := []PageConstructor{
// required
pages.NewTimezonePage,
pages.NewKeyboardPage,
pages.NewDiskConfigPage,
pages.NewUserAddPage,
pages.NewTelemetryPage,
// advanced
pages.NewBundlePage,
pages.NewHostnamePage,
pages.NewConfigKernelPage,
pages.NewSwupdConfigPage,
pages.NewNetworkPage,
// always last
pages.NewInstallPage,
}
// Create all pages
for _, f := range pageCreators {
page, err := f(window, window.model)
if err != nil {
return nil, err
}
if err = window.AddPage(page); err != nil {
return nil, err
}
}
// Show the whole window now
window.handle.ShowAll()
// Show the menu view
window.ShowMenuView()
return window, nil
}
// ShowMenuView displays the menu view
func (window *Window) ShowMenuView() {
done := window.menu.screens[ContentViewRequired].IsDone()
window.buttons.install.SetSensitive(done)
window.banner.Show()
window.menu.switcher.Show()
window.rootStack.SetVisibleChildName("menu")
window.menu.stack.SetVisibleChildName("required")
window.buttons.stack.SetVisibleChildName("primary")
}
// InitScreens initializes the switcher screens
func (window *Window) InitScreens() error {
var err error
// Set up required screen
if window.menu.screens[true], err = NewContentView(window); err != nil {
return err
}
window.menu.stack.AddNamed(window.menu.screens[ContentViewRequired].GetRootWidget(), "required")
// Set up non required screen
if window.menu.screens[false], err = NewContentView(window); err != nil {
return err
}
window.menu.stack.AddNamed(window.menu.screens[ContentViewAdvanced].GetRootWidget(), "advanced")
return nil
}
// AddPage adds the page to the relevant switcher screen
func (window *Window) AddPage(page pages.Page) error {
var (
err error
id int
)
id = page.GetID()
switch id {
case pages.PageIDWelcome:
window.menu.welcomePage = page
case pages.PageIDInstall:
window.menu.installPage = page
default: // Add to the required or advanced (optional) screen
err := window.menu.screens[page.IsRequired()].AddPage(page)
if err != nil {
return err
}
}
// Create a header page wrap for the page
header, err := PageHeaderNew(page)
if err != nil {
return err
}
root := header.GetRootWidget()
// Make available via root stack
window.pages[id] = root
window.rootStack.AddNamed(root, "page:"+string(id))
return nil
}
// createNavButton creates specialised navigation button
func createNavButton(label, style string) (*gtk.Button, error) {
button, err := gtk.ButtonNewWithLabel(label)
if err != nil {
return nil, err
}
sc, err := button.GetStyleContext()
if err != nil {
return nil, err
}
// Not able to set size using CSS
button.SetSizeRequest(100, 36)
sc.AddClass(style)
return button, nil
}
// CreateFooter creates the navigation footer area
func (window *Window) CreateFooter() error {
var err error
// Create stack for buttons
if window.buttons.stack, err = gtk.StackNew(); err != nil {
return err
}
// Set alignment up
window.buttons.stack.SetMarginTop(4)
window.buttons.stack.SetMarginBottom(6)
window.buttons.stack.SetMarginEnd(18)
window.buttons.stack.SetHAlign(gtk.ALIGN_FILL)
window.buttons.stack.SetTransitionType(gtk.STACK_TRANSITION_TYPE_CROSSFADE)
window.contentLayout.PackEnd(window.buttons.stack, false, false, 0)
// Create box for welcome page buttons
if window.buttons.boxWelcome, err = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0); err != nil {
return err
}
// Install button
if window.buttons.next, err = createNavButton(utils.Locale.Get("NEXT"), "button-confirm"); err != nil {
return err
}
_ = window.buttons.next.Connect("clicked", func() { window.onNextClick() })
// Exit button
if window.buttons.exit, err = createNavButton(utils.Locale.Get("EXIT"), "button-cancel"); err != nil {
return err
}
_ = window.buttons.exit.Connect("clicked", func() { gtk.MainQuit() })
// Pack the buttons
window.buttons.boxWelcome.PackEnd(window.buttons.next, false, false, 4)
window.buttons.boxWelcome.PackEnd(window.buttons.exit, false, false, 4)
// Add the boxes
window.buttons.stack.AddNamed(window.buttons.boxWelcome, "welcome")
return nil
}
// UpdateFooter updates the navigation footer area
func (window *Window) UpdateFooter() error {
var err error
// Install button
if window.buttons.install, err = createNavButton(utils.Locale.Get("INSTALL"), "button-confirm"); err != nil {
return err
}
_ = window.buttons.install.Connect("clicked", func() { window.confirmInstall() })
// Exit button
if window.buttons.quit, err = createNavButton(utils.Locale.Get("EXIT"), "button-cancel"); err != nil {
return err
}
_ = window.buttons.quit.Connect("clicked", func() { gtk.MainQuit() })
// Back button
if window.buttons.back, err = createNavButton(utils.Locale.Get("CHANGE LANGUAGE"), "button-cancel"); err != nil {
return err
}
_ = window.buttons.back.Connect("clicked", func() { window.launchWelcomeView() })
window.buttons.back.SetMarginStart(common.StartEndMargin)
// Confirm button
if window.buttons.confirm, err = createNavButton(utils.Locale.Get("CONFIRM"), "button-confirm"); err != nil {
return err
}
_ = window.buttons.confirm.Connect("clicked", func() { window.onConfirmClick() })
// Cancel button
if window.buttons.cancel, err = createNavButton(utils.Locale.Get("CANCEL"), "button-cancel"); err != nil {
return err
}
_ = window.buttons.cancel.Connect("clicked", func() { window.onCancelClick() })
// Create box for primary buttons
if window.buttons.boxPrimary, err = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0); err != nil {
return err
}
window.buttons.boxPrimary.PackEnd(window.buttons.install, false, false, 4)
window.buttons.boxPrimary.PackEnd(window.buttons.quit, false, false, 4)
window.buttons.boxPrimary.PackStart(window.buttons.back, false, false, 4)
// Create box for secondary buttons
if window.buttons.boxSecondary, err = gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0); err != nil {
return err
}
window.buttons.boxSecondary.PackEnd(window.buttons.confirm, false, false, 4)
window.buttons.boxSecondary.PackEnd(window.buttons.cancel, false, false, 4)
// Add the boxes
window.buttons.stack.AddNamed(window.buttons.boxPrimary, "primary")
window.buttons.stack.AddNamed(window.buttons.boxSecondary, "secondary")
return nil
}
// onNextClick handles the Next button click
func (window *Window) onNextClick() {
window.launchMenuView()
}
// onConfirmClick handles the Confirm button click.
func (window *Window) onConfirmClick() {
window.menu.currentPage.StoreChanges()
// Close the page only if the page is done.
if window.menu.currentPage.IsDone() {
window.closePage()
}
}
// onCancelClick handles the Cancel button click.
func (window *Window) onCancelClick() {
window.menu.currentPage.ResetChanges()
window.closePage()
}
// closePage closes the current page and displays the Menu page
func (window *Window) closePage() {
// Reset the SummaryWidget for responsible controller
window.menu.screens[window.menu.currentPage.IsRequired()].UpdateView(window.menu.currentPage)
// Reset currentPage
window.menu.currentPage = nil
// Switch UI back to primary view
window.rootStack.SetVisibleChildName("menu")
window.banner.Show()
window.menu.switcher.Show()
window.buttons.stack.SetVisibleChildName("primary")
// Enable installation if all required pages are done
requiredDone := window.menu.screens[ContentViewRequired].IsDone()
window.buttons.install.SetSensitive(requiredDone)
}
// ActivatePage customizes common widgets and displays the page
func (window *Window) ActivatePage(page pages.Page) {
id := page.GetID()
// Customize common widgets based on the page being loaded
switch id {
case pages.PageIDWelcome:
window.banner.Show()
window.buttons.stack.SetVisibleChildName("welcome")
case pages.PageIDInstall:
window.menu.switcher.Hide()
window.banner.Show()
window.banner.labelText.SetMarkup(GetThankYouMessage())
window.buttons.stack.SetVisibleChildName("primary")
window.buttons.install.Hide()
window.buttons.back.Hide()
sc, err := window.buttons.quit.GetStyleContext()
if err != nil {
log.Warning("Error getting style context: ", err) // Just log trivial error
} else {
sc.RemoveClass("button-cancel")
sc.AddClass("button-confirm")
}
window.buttons.quit.SetSensitive(false)
case pages.PageIDTelemetry:
window.menu.switcher.Hide()
window.banner.Hide()
window.buttons.stack.SetVisibleChildName("secondary")
window.buttons.confirm.SetSensitive(true)
window.buttons.confirm.SetCanDefault(true)
window.buttons.confirm.GrabDefault()
window.buttons.confirm.SetLabel(utils.Locale.Get("YES"))
window.buttons.cancel.SetLabel(utils.Locale.Get("NO"))
case pages.PageIDNetwork:
// Launches network check pop-up without changing page
if _, err := network.RunNetworkTest(window.model); err != nil {
log.Warning("Error running network test: ", err)
}
return
case pages.PageIDBundle:
window.menu.switcher.Hide()
window.banner.Hide()
window.buttons.stack.SetVisibleChildName("secondary")
window.buttons.confirm.SetSensitive(false)
window.buttons.confirm.SetLabel(utils.Locale.Get("CONFIRM"))
window.buttons.cancel.SetLabel(utils.Locale.Get("CANCEL"))
page.ResetChanges()
default:
window.menu.switcher.Hide()
window.banner.Hide()
window.buttons.stack.SetVisibleChildName("secondary")
window.buttons.confirm.SetSensitive(false)
window.buttons.confirm.SetLabel(utils.Locale.Get("CONFIRM"))
window.buttons.cancel.SetLabel(utils.Locale.Get("CANCEL"))
}
window.menu.currentPage = page
page.ResetChanges() // Allow page to take control now
window.rootStack.SetVisibleChild(window.pages[id]) // Set the root stack to show the new page
}
// SetButtonState is called by the pages to enable/disable certain buttons.
func (window *Window) SetButtonState(button pages.Button, enabled bool) {
switch button {
case pages.ButtonCancel:
window.buttons.cancel.SetSensitive(enabled)
case pages.ButtonConfirm:
window.buttons.confirm.SetSensitive(enabled)
case pages.ButtonQuit:
window.buttons.quit.SetSensitive(enabled)
case pages.ButtonBack:
window.buttons.back.SetSensitive(enabled)
case pages.ButtonNext:
window.buttons.next.SetSensitive(enabled)
case pages.ButtonExit:
window.buttons.exit.SetSensitive(enabled)
default:
log.Error("Undefined button")
}
}
// SetButtonVisible is called by the pages to view/hide certain buttons.
func (window *Window) SetButtonVisible(button pages.Button, visible bool) {
switch button {
case pages.ButtonCancel:
window.buttons.cancel.SetVisible(visible)
case pages.ButtonConfirm:
window.buttons.confirm.SetVisible(visible)
case pages.ButtonQuit:
window.buttons.quit.SetVisible(visible)
case pages.ButtonBack:
window.buttons.back.SetVisible(visible)
case pages.ButtonNext:
window.buttons.next.SetVisible(visible)
case pages.ButtonExit:
window.buttons.exit.SetVisible(visible)
default:
log.Error("Undefined button")
}
}
// launchWelcomeView launches the welcome view
func (window *Window) launchWelcomeView() {
window.mainLayout.Remove(window.contentContainer)
window.mainLayout.Remove(window.banner.GetRootWidget())
if _, err := window.createWelcomePage(); err != nil {
window.Panic(err)
}
}
// launchMenuView launches the menu view
func (window *Window) launchMenuView() {
log.Debug("Launching MenuView")
if window.menu.currentPage != nil {
window.menu.currentPage.StoreChanges()
}
if _, err := window.createMenuPages(); err != nil {
window.Panic(err)
}
}
// writeToConfirmInstallDialog is a helper function to write to dialog for confirm installation window
func writeToConfirmInstallDialog(buffer *gtk.TextBuffer, dryRunResults *storage.DryRunType) {
for _, media := range *dryRunResults.UnPlannedDestructiveResults {
log.Debug("OtherMediaChange: %s", media)
buffer.InsertMarkup(buffer.GetEndIter(),
"<b><span foreground=\"#FDB814\">"+media+"</span></b>\n")
}
for _, media := range *dryRunResults.TargetResults {
log.Debug("MediaChange: %s", media)
buffer.Insert(buffer.GetEndIter(), media+"\n")
}
}
func setConfirmButtonState(dialog *gtk.Dialog, window *Window) error {
var err error
if storage.GetImpactOnOtherDisks() && !window.model.MediaOpts.ForceDestructive {
if buttonIWidget, err := dialog.GetWidgetForResponse(gtk.RESPONSE_OK); err == nil {
confirmButton := buttonIWidget.ToWidget()
confirmButton.SetSensitive(false)
return nil
}
return err
}
return nil
}
// confirmInstall prompts the user for confirmation before installing
func (window *Window) confirmInstall() {
var primaryText, secondaryText string
eraseDisk := false
dataLoss := false
wholeDisk := false
// Build the string with the media being modified
targets := []string{}
if len(window.model.TargetMedias) == 0 {
targets = append(targets, utils.Locale.Get("None"))
} else {
for _, media := range window.model.TargetMedias {
targets = append(targets, media.GetDeviceFile())
if val, ok := window.model.InstallSelected[media.Name]; ok {
eraseDisk = eraseDisk || val.EraseDisk
dataLoss = dataLoss || val.DataLoss
wholeDisk = wholeDisk || val.WholeDisk
}
}
}
sort.Strings(targets)
if eraseDisk {
primaryText = utils.Locale.Get(storage.DestructiveWarning)
} else if dataLoss {
primaryText = utils.Locale.Get(storage.DataLossWarning)
} else if wholeDisk {
primaryText = utils.Locale.Get(storage.SafeWholeWarning)
} else {
primaryText = utils.Locale.Get(storage.SafePartialWarning)
}
contentBox, err := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
contentBox.SetHAlign(gtk.ALIGN_FILL)
contentBox.SetMarginBottom(common.TopBottomMargin)
if err != nil {
log.Error("Error creating box", err)
return
}
secondaryText = utils.Locale.Get("Target Media") + ": " + strings.Join(targets, ", ")
title := utils.Locale.Get(storage.ConfirmInstallation)
style := ""
if eraseDisk {
style = "label-error"
}
pLabel, err := common.SetLabel(primaryText, style, 0)
if err != nil {
log.Error("Error creating pLabel", err)
return
}
pLabel.SetUseMarkup(true)
pLabel.SetHAlign(gtk.ALIGN_START)
contentBox.PackStart(pLabel, false, true, 2)
sLabel, err := common.SetLabel(secondaryText, "", 0)
if err != nil {
log.Error("Error creating sLabel", err)
return
}
sLabel.SetUseMarkup(true)
sLabel.SetHAlign(gtk.ALIGN_START)
contentBox.PackStart(sLabel, false, true, 0)
textArea, err := gtk.TextViewNew()
if err != nil {
log.Error("Error creating textArea", err)
return
}
textArea.SetEditable(false)
buffer, err := textArea.GetBuffer()
if err != nil {
log.Error("Error getter textArea buffer", err)
return
}
// Set up the scroller
scroll, err := gtk.ScrolledWindowNew(nil, nil)
if err != nil {
log.Error("Error creating ScrolledWindow", err)
return
}
scroll.SetMarginTop(20)
// Set the scroll policy
scroll.SetPolicy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
// Set shadow type
scroll.SetShadowType(gtk.SHADOW_NONE)
scroll.SetSizeRequest(300, 200)
scroll.Add(textArea)
contentBox.PackStart(scroll, false, true, 0)
dialog, err := common.CreateDialogOkCancel(contentBox, title,
utils.Locale.Get("CONFIRM"), utils.Locale.Get("CANCEL"))
if err != nil {
log.Error("Error creating dialog", err)
return
}
_ = dialog.Connect("response", window.dialogResponse)
// Valid network is required to install without offline content or additional bundles
if (!swupd.OfflineIsUsable(utils.VersionUintString(window.model.Version), window.options) ||
len(window.model.UserBundles) != 0) && !controller.NetworkPassing {
if ret, err := network.RunNetworkTest(window.model); ret == network.NetTestErr {
log.Warning("Error running network test: ", err)
return
}
}
if !controller.NetworkPassing {
if !swupd.OfflineIsUsable(utils.VersionUintString(window.model.Version), window.options) {
// Cannot install without network or offline content
return
} else if len(window.model.UserBundles) != 0 {
// Cannot install without network and additional bundles. Allow user to remove bundles
// and continue the install
offlineMsg := utils.Locale.Get("Offline Install: Removing additional bundles")
buffer.Insert(buffer.GetEndIter(), offlineMsg+"\n")
buttonIWidget, err := dialog.GetWidgetForResponse(gtk.RESPONSE_OK)
if err != nil {
log.Error("Error getting confirm button", err)
return
}
confirmButton := buttonIWidget.ToWidget()
_ = confirmButton.Connect("button-press-event", func() {
window.model.UserBundles = nil
})
}
}
dryRunResults := storage.GetPlannedMediaChanges(window.model.InstallSelected, window.model.TargetMedias,
window.model.MediaOpts)
writeToConfirmInstallDialog(buffer, dryRunResults)
if err = setConfirmButtonState(dialog, window); err != nil {
log.Error("Error setting Confirm button state", err)
}
dialog.ShowAll()
dialog.Run()
}
// dialogResponse handles the response from the dialog message
func (window *Window) dialogResponse(msgDialog *gtk.Dialog, responseType gtk.ResponseType) {
if responseType == gtk.RESPONSE_OK {
window.ActivatePage(window.menu.installPage)
}
msgDialog.Destroy()
}
// GetOptions returns the options given to the window
func (window *Window) GetOptions() args.Args {
return window.options
}
// GetRootDir returns the root dir
func (window *Window) GetRootDir() string {
return window.rootDir
}
// GetScanChannel is the getter for ScanInfo Channel
func (window *Window) GetScanChannel() chan bool {
return window.scanInfo.Channel
}
// GetScanDone is the getter for ScanInfo Done
func (window *Window) GetScanDone() bool {
return window.scanInfo.Done
}
// SetScanDone is the setter for ScanInfo Done
func (window *Window) SetScanDone(done bool) {
window.scanInfo.Done = done
}
// GetScanMedia is the getter for ScanInfo Media
func (window *Window) GetScanMedia() []*storage.BlockDevice {
return window.scanInfo.Media
}
// SetScanMedia is the setter for ScanInfo Media
func (window *Window) SetScanMedia(scannedMedia []*storage.BlockDevice) {
window.scanInfo.Media = scannedMedia
}
// Panic handles the gui crashes
func (window *Window) Panic(err error) {
log.Debug("Panic")
if errLog := window.model.Telemetry.LogRecord("guipanic", 3, err.Error()); errLog != nil {
log.Error("Failed to log Telemetry fail record: %s", "guipanic")
}
log.RequestCrashInfo()
displayErrorDialog(err)
}
// GetWelcomeMessage gets the welcome message
func GetWelcomeMessage() string {
text := "<span font-size='xx-large'>" +
utils.Locale.Get("Welcome to Clear Linux* OS Desktop Installation") + "</span>"
if model.Version != model.DemoVersion {
text += "\n\n<small>" + utils.Locale.Get("VERSION %s", model.Version) + "</small>"
}
return text
}
// GetThankYouMessage gets the thank you message
func GetThankYouMessage() string {
text := "<span font-size='xx-large'>" +
utils.Locale.Get("Thank you for choosing Clear Linux* OS") + "</span>"
if model.Version != model.DemoVersion {
text += "\n\n<small>" + utils.Locale.Get("VERSION %s", model.Version) + "</small>"
}
return text
}
func displayErrorDialog(err error) {
contentBox, err := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
contentBox.SetHAlign(gtk.ALIGN_FILL)
if err != nil {
log.Error("Error creating box", err)
return
}
label, err := common.SetLabel(log.GetCrashInfoMsg(), "label-error", 0.0)
if err != nil {
log.Error("Error creating label", err)
return
}
label.SetHAlign(gtk.ALIGN_CENTER)
label.SetMarginBottom(common.TopBottomMargin)
label.SetSelectable(true)
contentBox.PackStart(label, true, true, 0)
title := utils.Locale.Get("Something went wrong...")
dialog, err := common.CreateDialogOneButton(contentBox, title, utils.Locale.Get("OK"), "button-confirm")
if err != nil {
log.Error("Error creating dialog", err)
return
}
_ = dialog.Connect("response", func() {
dialog.Destroy()
gtk.MainQuit() // Exit Installer
})
dialog.ShowAll()
dialog.Run()
}
// displaySyscheckDialog creates a pop-up for system check failures
func displaySyscheckDialog(syscheckErr error) {
log.Error("System check failed: %s", syscheckErr.Error())
// Create box
contentBox, err := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
if err != nil {
log.Error("Error creating box", err)
return
}
contentBox.SetHAlign(gtk.ALIGN_FILL)
contentBox.SetMarginBottom(common.TopBottomMargin)
// Create label
text := utils.Locale.Get("System failed to pass pre-install checks.")
label, err := gtk.LabelNew(text)
if err != nil {
log.Error("Error creating label", err)
return
}
label.SetUseMarkup(true)
label.SetHAlign(gtk.ALIGN_START)
contentBox.PackStart(label, false, true, 0)
// Fail message label
text = syscheckErr.Error()
label, err = gtk.LabelNew(text)
if err != nil {
log.Error("Error creating specific label", err)
return
}
label.SetUseMarkup(true)
label.SetHAlign(gtk.ALIGN_START)
contentBox.PackStart(label, false, true, 0)
// Create dialog
title := utils.Locale.Get("Warning")
dialog, err := common.CreateDialogOneButton(contentBox, title, utils.Locale.Get("OK"), "button-confirm")
if err != nil {
log.Error("Error creating dialog", err)
return
}
dialog.SetDeletable(false)
// Configure button
buttonIWidget, err := dialog.GetWidgetForResponse(gtk.RESPONSE_OK)
if err != nil {
log.Error("Error getting confirm button", err)
return
}
confirmButton := buttonIWidget.ToWidget()
_ = confirmButton.Connect("button-press-event", func() {
dialog.Destroy()
gtk.MainQuit() // Exit Installer
})
dialog.ShowAll()
dialog.Run()
}