add feature page

Allow for additional settings that are not just software packages.

Fixes #13
This commit is contained in:
Peter Eisenmann
2022-11-11 02:28:25 +01:00
parent 5793ac3038
commit 2db28208ac
14 changed files with 282 additions and 2 deletions

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 6.5 1 c -0.832031 0 -1.5 0.667969 -1.5 1.5 v 1.5 h -3 c -0.554688 0 -1 0.445312 -1 1 v 3 h 1.5 c 0.832031 0 1.5 0.667969 1.5 1.5 s -0.667969 1.5 -1.5 1.5 h -1.5 v 3 c 0 0.554688 0.445312 1 1 1 h 3 v -1.5 c 0 -0.832031 0.667969 -1.5 1.5 -1.5 s 1.5 0.667969 1.5 1.5 v 1.5 h 3 c 0.554688 0 1 -0.445312 1 -1 v -3 h 1.5 c 0.832031 0 1.5 -0.667969 1.5 -1.5 s -0.667969 -1.5 -1.5 -1.5 h -1.5 v -3 c 0 -0.554688 -0.445312 -1 -1 -1 h -3 v -1.5 c 0 -0.832031 -0.667969 -1.5 -1.5 -1.5 z m 0 0" fill="#888888"/></svg>

After

Width:  |  Height:  |  Size: 643 B

View File

@@ -9,6 +9,7 @@ blueprints = custom_target('blueprints',
'ui/pages/done.blp',
'ui/pages/encrypt.blp',
'ui/pages/failed.blp',
'ui/pages/feature.blp',
'ui/pages/format.blp',
'ui/pages/install.blp',
'ui/pages/internet.blp',

View File

@@ -13,6 +13,7 @@
<file preprocess="xml-stripblanks">icon/scalable/actions/language-symbolic.svg</file>
<file preprocess="xml-stripblanks">icon/scalable/actions/map-symbolic.svg</file>
<file preprocess="xml-stripblanks">icon/scalable/actions/no-disk-symbolic.svg</file>
<file preprocess="xml-stripblanks">icon/scalable/actions/puzzle-piece-symbolic.svg</file>
<file preprocess="xml-stripblanks">icon/scalable/actions/question-round-symbolic.svg</file>
<file preprocess="xml-stripblanks">icon/scalable/actions/success-symbolic.svg</file>
<file preprocess="xml-stripblanks">icon/scalable/actions/user-symbolic.svg</file>
@@ -27,6 +28,7 @@
<file preprocess="xml-stripblanks">ui/pages/done.ui</file>
<file preprocess="xml-stripblanks">ui/pages/encrypt.ui</file>
<file preprocess="xml-stripblanks">ui/pages/failed.ui</file>
<file preprocess="xml-stripblanks">ui/pages/feature.ui</file>
<file preprocess="xml-stripblanks">ui/pages/format.ui</file>
<file preprocess="xml-stripblanks">ui/pages/install.ui</file>
<file preprocess="xml-stripblanks">ui/pages/internet.ui</file>

View File

@@ -0,0 +1,35 @@
using Gtk 4.0;
template FeaturePage : Box {
orientation: vertical;
spacing: 12;
Label {
/* Translators: Page title */
label: _("Additional Features");
justify: center;
wrap: true;
styles ["heading"]
}
ScrolledWindow {
propagate-natural-height: true;
styles ["embedded", "separate-bottom"]
child: ListBox list {
hexpand: true;
selection-mode: none;
row-activated => row_activated();
styles ["boxed-list"]
};
}
Button {
/* Translators: On button. */
label: _("_Continue");
focusable: true;
halign: center;
use-underline: true;
clicked => continue();
styles ["suggested-action", "pill", "bottom-button"]
}
}

View File

@@ -167,6 +167,46 @@ template SummaryPage : Box {
icon-name: "emblem-system-symbolic";
}
}
Adw.ActionRow feature_row {
name: "feature";
activatable: true;
/* Translators: Description of selected additional software. */
title: _("Additional Features");
Stack feature_stack {
vhomogeneous: false;
transition-type: crossfade;
styles ["row-content"]
StackPage {
name: "used";
child: ListBox feature_list {
halign: end;
valign: center;
selection-mode: none;
styles ["nested-list"]
};
}
StackPage {
name: "none";
child: Label {
/* Translators: Shown when list of selected feature is empty. */
label: _("None");
hexpand: true;
valign: center;
wrap: true;
xalign: 1;
styles ["dim-label"]
};
}
}
Image {
icon-name: "emblem-system-symbolic";
}
}
};
}

View File

@@ -100,6 +100,31 @@ additional_software:
name_jp : '入力プログラム'
icon_path : '/etc/os-installer/icons/jp-symbol.svg'
# List of features that can additionally be selected. Very similar
# to `additional_software`, but meant for more generic features. Can
# be used instead of or in combination with `additional_software`.
#
# feature string Forwarded to the installation script as is.
# suggested bool Optional. Whether installation defaults to yes.
# name_LC string Name presented to user. Translatable.
# If no (English) name is available, it will only
# be shown for translated languages.
# description_LC string Optional. Description presented to user. Translatable.
# icon_path string Optional. Absolute path to icon to be displayed.
#
# Default: [], suggested: False, description: '', icon_path: no icon
additional_features:
- feature : 'snapshots'
suggested : yes
name : 'Snapshots'
name_et : 'Vahepildid'
description : 'Snapshots allow restoring a previous state of your system'
description_et : 'Vahepildid võimaldavad taastada teie süsteemi eelmise seisundi'
icon_path : '/etc/os-installer/icons/snapshot.svg'
- feature : 'dummy'
name : 'Dummy'
description : 'This does not do anything'
# Upon failure an option to search for help on the internet is given.
# The url this leads to can be defined here. The squiggly brackets are
# replaced with the os-installer version.

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="32"
viewBox="0 0 32 32"
width="32"
version="1.1"
id="svg4"
sodipodi:docname="snapshot.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="13.40625"
inkscape:cx="28.606061"
inkscape:cy="32"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g6571" />
<g
id="g6571"
transform="scale(4)">
<circle
style="fill:#ffffff;fill-opacity:1;stroke-width:0.5;stroke-dasharray:none"
id="path8389"
cx="4"
cy="4"
r="4" />
<path
d="M 4,2 C 3.475586,2 2.972656,2.206055 2.598633,2.573242 L 3.27832,3.75 4.276367,2.021484 C 4.18457,2.007812 4.092773,2.000976 4,2 Z M 4.53418,2.0752 3.855469,3.25 h 1.99707 C 5.61914,2.674805 5.132812,2.240234 4.53418,2.075195 Z M 2.424805,2.771489 C 2.15039,3.12207 2.000977,3.554687 2,4 2,4.168945 2.02246,4.336914 2.06543,4.5 H 3.422852 Z M 4.577148,3.5 5.575195,5.228515 C 5.849609,4.87793 5.999023,4.445312 6,4 6,3.831055 5.97754,3.663086 5.93457,3.5 Z M 4.72168,4.25 3.723633,5.978515 C 3.815433,5.992187 3.907226,5.999023 4,6 4.524414,6 5.027344,5.793946 5.401367,5.425782 Z M 2.147461,4.75 C 2.380859,5.325195 2.867187,5.759765 3.46582,5.924805 L 4.144531,4.75 Z m 0,0"
fill="#222222"
id="path8380"
style="stroke-width:0.25" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -10,6 +10,7 @@
# OSI_FORMATS : Locale of formats to be used
# OSI_TIMEZONE : Timezone to be used
# OSI_ADDITIONAL_SOFTWARE: Space-separated list of additional packages to install
# OSI_ADDITIONAL_FEATURES: Space-separated list of additional features chosen
# sanity check that all variables were set
if [ -z ${OSI_LOCALE+x} ] || \
@@ -23,7 +24,8 @@ if [ -z ${OSI_LOCALE+x} ] || \
[ -z ${OSI_USER_PASSWORD+x} ] || \
[ -z ${OSI_FORMATS+x} ] || \
[ -z ${OSI_TIMEZONE+x} ] || \
[ -z ${OSI_ADDITIONAL_SOFTWARE+x} ]
[ -z ${OSI_ADDITIONAL_SOFTWARE+x} ] || \
[ -z ${OSI_ADDITIONAL_FEATURES+x} ]
then
echo "Installer script called without all environment variables set!"
exit 1
@@ -44,6 +46,7 @@ echo 'OSI_USER_PASSWORD ' $OSI_USER_PASSWORD
echo 'OSI_FORMATS ' $OSI_FORMATS
echo 'OSI_TIMEZONE ' $OSI_TIMEZONE
echo 'OSI_ADDITIONAL_SOFTWARE ' $OSI_ADDITIONAL_SOFTWARE
echo 'OSI_ADDITIONAL_FEATURES ' $OSI_ADDITIONAL_FEATURES
echo ''
# Pretending to do something

View File

@@ -54,6 +54,8 @@ def _load_default_config():
'skip_locale': False,
# software
'additional_software': [],
# feature
'additional_features': [],
# fail
'failure_help_url': 'https://duckduckgo.com/?q="os-installer {}"+"failed installation"',
# commands
@@ -77,6 +79,8 @@ def _load_optional_defaults(config):
config['timezone'] = 'UTC'
config['chosen_software_packages'] = ''
config['chosen_software'] = []
config['chosen_features'] = ''
config['chosen_feature_names'] = []
def _set_testing_defaults(config):
@@ -105,6 +109,7 @@ def _valid(config):
_match(config, 'minimum_disk_size', int) and
_match(config, 'offer_disk_encryption', bool) and
_match(config, 'additional_software', list) and
_match(config, 'additional_features', list) and
_match(config, 'distribution_name', str) and
_match(config, 'fixed_language', bool, str))
@@ -155,5 +160,6 @@ def create_envs(config, with_install_envs, with_configure_envs):
f'OSI_FORMATS={config["formats_locale"]}',
f'OSI_TIMEZONE={config["timezone"]}',
f'OSI_ADDITIONAL_SOFTWARE={config["chosen_software_packages"]}',
f'OSI_ADDITIONAL_FEATURES={config["chosen_features"]}',
]
return envs + [None]

View File

@@ -23,6 +23,7 @@ os_installer_sources = [
'global_state.py',
'main.py',
'provider/disk_provider.py',
'provider/feature_provider.py',
'provider/format_provider.py',
'provider/internet_provider.py',
'provider/keyboard_layout_provider.py',
@@ -36,6 +37,7 @@ os_installer_sources = [
'ui/pages/done.py',
'ui/pages/encrypt.py',
'ui/pages/failed.py',
'ui/pages/feature.py',
'ui/pages/format.py',
'ui/pages/install.py',
'ui/pages/internet.py',

View File

@@ -0,0 +1,51 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from gi.repository import GObject
from .global_state import global_state
class Feature(GObject.GObject):
__gtype_name__ = __qualname__
def __init__(self, feature, suggested, name, description, icon_path):
super().__init__()
self.feature = feature
self.suggested = suggested
self.name = name
self.description = description
self.icon_path = icon_path
### public methods ###
def get_feature_suggestions():
if not (features := global_state.get_config('additional_features')):
return []
language_code = global_state.get_config('language_code')
suggestions = []
for feature in features:
if not 'feature' in feature:
print(f'feature {feature} not correctly configured!')
continue
if (not ((name_key := f'name_{language_code}') in feature or
(name_key := 'name') in feature)):
# no error if feature is only suggested for specific translations
if not any(key.startswith('name') for key in feature.keys()):
print(f'feature {feature} not correctly configured!')
continue
suggested = feature['suggested'] if 'suggested' in feature else False
name = feature[name_key]
if ((description_key := f'description_{language_code}') in feature or
(description_key := 'description') in feature):
description = feature[description_key]
else:
description = ''
icon_path = feature['icon_path'] if 'icon_path' in feature else ''
suggestions.append(
Feature(feature['feature'], suggested, name, description, icon_path))
return suggestions

46
src/ui/pages/feature.py Normal file
View File

@@ -0,0 +1,46 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from gi.repository import Gio, Gtk
from .global_state import global_state
from .page import Page
from .feature_provider import get_feature_suggestions
from .widgets import reset_model, SelectionRow
@Gtk.Template(resource_path='/com/github/p3732/os-installer/ui/pages/feature.ui')
class FeaturePage(Gtk.Box, Page):
__gtype_name__ = __qualname__
image = 'puzzle-piece-symbolic'
list = Gtk.Template.Child()
list_model = Gio.ListStore()
def __init__(self, **kwargs):
Gtk.Box.__init__(self, **kwargs)
self.list.bind_model(
self.list_model,
lambda feat: SelectionRow(feat.name, feat.description, feat.icon_path,
feat.suggested, feat, 'puzzle-piece-symbolic'))
### callbacks ###
@Gtk.Template.Callback('continue')
def _continue(self, button):
global_state.advance(self)
@Gtk.Template.Callback('row_activated')
def _row_activated(self, list_box, row):
row.flip_switch()
### public methods ###
def load_once(self):
suggestions = get_feature_suggestions()
reset_model(self.list_model, suggestions)
def unload(self):
choices = [row.info for row in self.list if row.is_activated()]
features = ' '.join([choice.feature for choice in choices])
global_state.set_config('chosen_feature_names', choices)
global_state.set_config('chosen_features', features)

View File

@@ -5,7 +5,6 @@ from gi.repository import Gio, Gtk
from .global_state import global_state
from .installation_scripting import installation_scripting, Step
from .page import Page
#from .software_provider import get_software_suggestions
from .widgets import reset_model, SoftwareSummaryRow
@@ -21,6 +20,7 @@ class SummaryPage(Gtk.Box, Page):
format_row = Gtk.Template.Child()
timezone_row = Gtk.Template.Child()
software_row = Gtk.Template.Child()
feature_row = Gtk.Template.Child()
# row content
language_label = Gtk.Template.Child()
@@ -35,12 +35,20 @@ class SummaryPage(Gtk.Box, Page):
software_list = Gtk.Template.Child()
software_model = Gio.ListStore()
# feature list
feature_stack = Gtk.Template.Child()
feature_list = Gtk.Template.Child()
feature_model = Gio.ListStore()
def __init__(self, **kwargs):
Gtk.Box.__init__(self, **kwargs)
self.software_list.bind_model(
self.software_model, lambda pkg: SoftwareSummaryRow(pkg.name, pkg.icon_path))
self.feature_list.bind_model(
self.feature_model, lambda pkg: SoftwareSummaryRow(pkg.name, pkg.icon_path))
self.language_row.set_visible(global_state.get_config('fixed_language'))
self.software_row.set_visible(global_state.get_config('additional_software'))
self.feature_row.set_visible(global_state.get_config('additional_features'))
self.user_row.set_visible(not global_state.get_config('skip_user'))
self.format_row.set_visible(not global_state.get_config('skip_locale'))
self.timezone_row.set_visible(not global_state.get_config('skip_locale'))
@@ -75,4 +83,11 @@ class SummaryPage(Gtk.Box, Page):
else:
self.software_stack.set_visible_child_name('none')
features = global_state.get_config('chosen_feature_names')
if len(features) > 0:
self.feature_stack.set_visible_child_name('used')
reset_model(self.feature_model, features)
else:
self.feature_stack.set_visible_child_name('none')
return "prevent_back_navigation"

View File

@@ -13,6 +13,7 @@ from .disk import DiskPage
from .done import DonePage
from .encrypt import EncryptPage
from .failed import FailedPage
from .feature import FeaturePage
from .format import FormatPage
from .install import InstallPage
from .internet import InternetPage
@@ -107,6 +108,7 @@ class OsInstallerWindow(Adw.ApplicationWindow):
('format', FormatPage, not global_state.get_config('skip_locale')),
('timezone', TimezonePage, not global_state.get_config('skip_locale')),
('software', SoftwarePage, global_state.get_config('additional_software')),
('feature', FeaturePage, global_state.get_config('additional_features')),
# summary
('summary', SummaryPage, True),
# installation