mirror of
https://github.com/clearlinux/clr-installer.git
synced 2026-09-03 20:21:27 +00:00
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>
1061 lines
29 KiB
Go
1061 lines
29 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)
|
|
|
|
// 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
|
|
_, err = window.handle.Connect("destroy", func() {
|
|
gtk.MainQuit()
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 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 {
|
|
_, err = glib.IdleAdd(func() {
|
|
displaySyscheckDialog(syscheckErr)
|
|
})
|
|
if err != nil {
|
|
log.ErrorError(err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
if _, err = window.buttons.next.Connect("clicked", func() { window.onNextClick() }); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Exit button
|
|
if window.buttons.exit, err = createNavButton(utils.Locale.Get("EXIT"), "button-cancel"); err != nil {
|
|
return err
|
|
}
|
|
if _, err = window.buttons.exit.Connect("clicked", func() { gtk.MainQuit() }); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
if _, err = window.buttons.install.Connect("clicked", func() { window.confirmInstall() }); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Exit button
|
|
if window.buttons.quit, err = createNavButton(utils.Locale.Get("EXIT"), "button-cancel"); err != nil {
|
|
return err
|
|
}
|
|
if _, err = window.buttons.quit.Connect("clicked", func() { gtk.MainQuit() }); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Back button
|
|
if window.buttons.back, err = createNavButton(utils.Locale.Get("CHANGE LANGUAGE"), "button-cancel"); err != nil {
|
|
return err
|
|
}
|
|
if _, err = window.buttons.back.Connect("clicked", func() { window.launchWelcomeView() }); err != nil {
|
|
return err
|
|
}
|
|
|
|
window.buttons.back.SetMarginStart(common.StartEndMargin)
|
|
|
|
// Confirm button
|
|
if window.buttons.confirm, err = createNavButton(utils.Locale.Get("CONFIRM"), "button-confirm"); err != nil {
|
|
return err
|
|
}
|
|
if _, err = window.buttons.confirm.Connect("clicked", func() { window.onConfirmClick() }); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Cancel button
|
|
if window.buttons.cancel, err = createNavButton(utils.Locale.Get("CANCEL"), "button-cancel"); err != nil {
|
|
return err
|
|
}
|
|
if _, err = window.buttons.cancel.Connect("clicked", func() { window.onCancelClick() }); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
_, err = dialog.Connect("response", window.dialogResponse)
|
|
if err != nil {
|
|
log.Error("Error connecting to dialog", err)
|
|
return
|
|
}
|
|
|
|
// Valid network is required to install without offline content or additional bundles
|
|
if (!swupd.IsOfflineContent() || 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.IsOfflineContent() {
|
|
// 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")
|
|
|
|
confirmButton, err := dialog.GetWidgetForResponse(gtk.RESPONSE_OK)
|
|
if err != nil {
|
|
log.Error("Error getting confirm button", err)
|
|
return
|
|
}
|
|
_, err = confirmButton.Connect("button-press-event", func() {
|
|
window.model.UserBundles = nil
|
|
})
|
|
if err != nil {
|
|
log.Error("Error connecting to dialog", err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
medias := storage.GetPlannedMediaChanges(window.model.InstallSelected, window.model.TargetMedias)
|
|
for _, media := range medias {
|
|
log.Debug("MediaChange: %s", media)
|
|
buffer.Insert(buffer.GetEndIter(), media+"\n")
|
|
}
|
|
|
|
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
|
|
}
|
|
_, err = dialog.Connect("response", func() {
|
|
dialog.Destroy()
|
|
gtk.MainQuit() // Exit Installer
|
|
})
|
|
if err != nil {
|
|
log.Error("Error connecting to dialog", err)
|
|
return
|
|
}
|
|
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
|
|
confirmButton, err := dialog.GetWidgetForResponse(gtk.RESPONSE_OK)
|
|
if err != nil {
|
|
log.Error("Error getting confirm button", err)
|
|
return
|
|
}
|
|
_, err = confirmButton.Connect("button-press-event", func() {
|
|
dialog.Destroy()
|
|
gtk.MainQuit() // Exit Installer
|
|
})
|
|
if err != nil {
|
|
log.Error("Error connecting to dialog", err)
|
|
return
|
|
}
|
|
|
|
dialog.ShowAll()
|
|
dialog.Run()
|
|
}
|