Send patches - preferably formatted by git format-patch - to patches at archlinux32 dot org.
summaryrefslogtreecommitdiff
path: root/archinstall/lib/menu
diff options
context:
space:
mode:
authorDaniel Girtler <blackrabbit256@gmail.com>2022-06-09 22:54:12 +1000
committerGitHub <noreply@github.com>2022-06-09 14:54:12 +0200
commit0bdb46f3081aaed095f87e35b18b3714d8782ed1 (patch)
tree894ef3050fdfae1b455fea5ad06aac980c9e26a5 /archinstall/lib/menu
parentaec86eb04e96f4c178cf89f7e72a257d12019fdc (diff)
Fancy user interface (#1320)
* Display submenus as tables * Update * Update * Update * Update Co-authored-by: Daniel Girtler <girtler.daniel@gmail.com>
Diffstat (limited to 'archinstall/lib/menu')
-rw-r--r--archinstall/lib/menu/global_menu.py16
-rw-r--r--archinstall/lib/menu/list_manager.py100
-rw-r--r--archinstall/lib/menu/menu.py21
3 files changed, 86 insertions, 51 deletions
diff --git a/archinstall/lib/menu/global_menu.py b/archinstall/lib/menu/global_menu.py
index b73fb48f..1a292476 100644
--- a/archinstall/lib/menu/global_menu.py
+++ b/archinstall/lib/menu/global_menu.py
@@ -163,7 +163,8 @@ class GlobalMenu(GeneralMenu):
Selector(
_('Network configuration'),
ask_to_configure_network,
- display_func=lambda x: self._prev_network_configuration(x),
+ display_func=lambda x: self._display_network_conf(x),
+ preview_func=self._prev_network_config,
default={})
self._menu_options['timezone'] = \
Selector(
@@ -226,16 +227,23 @@ class GlobalMenu(GeneralMenu):
return _('Install ({} config(s) missing)').format(missing)
return _('Install')
- def _prev_network_configuration(self, cur_value: Union[NetworkConfiguration, List[NetworkConfiguration]]) -> str:
+ def _display_network_conf(self, cur_value: Union[NetworkConfiguration, List[NetworkConfiguration]]) -> str:
if not cur_value:
return _('Not configured, unavailable unless setup manually')
else:
if isinstance(cur_value, list):
- ifaces = [x.iface for x in cur_value]
- return f'Configured ifaces: {ifaces}'
+ return str(_('Configured {} interfaces')).format(len(cur_value))
else:
return str(cur_value)
+ def _prev_network_config(self) -> Optional[str]:
+ selector = self._menu_options['nic']
+ if selector.has_selection():
+ ifaces = selector.current_selection
+ if isinstance(ifaces, list):
+ return FormattedOutput.as_table(ifaces)
+ return None
+
def _prev_harddrives(self) -> Optional[str]:
selector = self._menu_options['harddrives']
if selector.has_selection():
diff --git a/archinstall/lib/menu/list_manager.py b/archinstall/lib/menu/list_manager.py
index 40d01ce3..fe491caa 100644
--- a/archinstall/lib/menu/list_manager.py
+++ b/archinstall/lib/menu/list_manager.py
@@ -86,7 +86,7 @@ The contents in the base class of this methods serve for a very basic usage, and
"""
import copy
from os import system
-from typing import Union, Any, TYPE_CHECKING, Dict, Optional
+from typing import Union, Any, TYPE_CHECKING, Dict, Optional, Tuple, List
from .text_input import TextInput
from .menu import Menu
@@ -135,9 +135,9 @@ class ListManager:
elif isinstance(default_action,(list,tuple)):
self._default_action = default_action
else:
- self._default_action = [str(default_action),]
+ self._default_action = [str(default_action)]
- self._header = header if header else None
+ self._header = header if header else ''
self._cancel_action = str(_('Cancel'))
self._confirm_action = str(_('Confirm and exit'))
self._separator = ''
@@ -155,61 +155,81 @@ class ListManager:
while True:
# this will return a dictionary with the key as the menu entry to be displayed
# and the value is the original value from the self._data container
-
data_formatted = self.reformat(self._data)
- options = list(data_formatted.keys())
-
- if len(options) > 0:
- options.append(self._separator)
+ options, header = self._prepare_selection(data_formatted)
- if self._default_action:
- options += self._default_action
+ menu_header = self._header
- options += self._bottom_list
+ if header:
+ menu_header += header
system('clear')
- target = Menu(
+ choice = Menu(
self._prompt,
options,
sort=False,
clear_screen=False,
clear_menu_on_exit=False,
- header=self._header,
+ header=header,
skip_empty_entries=True,
- skip=False
+ skip=False,
+ show_search_hint=False
).run()
- if not target.value or target.value in self._bottom_list:
- self.action = target
+ if not choice.value or choice.value in self._bottom_list:
+ self.action = choice
break
- if target.value and target.value in self._default_action:
- self.action = target.value
+ if choice.value and choice.value in self._default_action:
+ self.action = choice.value
self.target = None
self._data = self.exec_action(self._data)
continue
- if isinstance(self._data,dict):
- data_key = data_formatted[target.value]
+ if isinstance(self._data, dict):
+ data_key = data_formatted[choice.value]
key = self._data[data_key]
self.target = {data_key: key}
elif isinstance(self._data, list):
- self.target = [d for d in self._data if d == data_formatted[target.value]][0]
+ self.target = [d for d in self._data if d == data_formatted[choice.value]][0]
else:
- self.target = self._data[data_formatted[target.value]]
+ self.target = self._data[data_formatted[choice.value]]
# Possible enhancement. If run_actions returns false a message line indicating the failure
- self.run_actions(target.value)
+ self.run_actions(choice.value)
- if target.value == self._cancel_action: # TODO dubious
+ if choice.value == self._cancel_action:
return self._original_data # return the original list
else:
return self._data
- def run_actions(self,prompt_data=None):
+ def _prepare_selection(self, data_formatted: Dict[str, Any]) -> Tuple[List[str], str]:
+ # header rows are mapped to None so make sure
+ # to exclude those from the selectable data
+ options: List[str] = [key for key, val in data_formatted.items() if val is not None]
+ header = ''
+
+ if len(options) > 0:
+ table_header = [key for key, val in data_formatted.items() if val is None]
+ header = '\n'.join(table_header)
+
+ if len(options) > 0:
+ options.append(self._separator)
+
+ if self._default_action:
+ # done only for mypy -> todo fix the self._default_action declaration
+ options += [action for action in self._default_action if action]
+
+ options += self._bottom_list
+ return options, header
+
+ def run_actions(self,prompt_data=''):
options = self.action_list() + self._bottom_item
- prompt = _("Select an action for < {} >").format(prompt_data if prompt_data else self.target)
+ display_value = self.selected_action_display(self.target) if self.target else prompt_data
+
+ prompt = _("Select an action for '{}'").format(display_value)
+
choice = Menu(
prompt,
options,
@@ -225,26 +245,28 @@ class ListManager:
if self.action and self.action != self._cancel_action:
self._data = self.exec_action(self._data)
- """
- The following methods are expected to be overwritten by the user if the needs of the list are beyond the simple case
- """
+ def selected_action_display(self, selection: Any) -> str:
+ # this will return the value to be displayed in the
+ # "Select an action for '{}'" string
+ raise NotImplementedError('Please implement me in the child class')
- def reformat(self, data: Any) -> Dict[str, Any]:
- """
- method to get the data in a format suitable to be shown
- It is executed once for run loop and processes the whole self._data structure
- """
- if isinstance(data,dict):
- return {f'{k}: {v}': k for k, v in data.items()}
- else:
- return {str(k): k for k in data}
+ def reformat(self, data: List[Any]) -> Dict[str, Any]:
+ # this should return a dictionary of display string to actual data entry
+ # mapping; if the value for a given display string is None it will be used
+ # in the header value (useful when displaying tables)
+ raise NotImplementedError('Please implement me in the child class')
def action_list(self):
"""
can define alternate action list or customize the list for each item.
Executed after any item is selected, contained in self.target
"""
- return self._base_actions
+ active_entry = self.target if self.target else None
+
+ if active_entry is None:
+ return [self._base_actions[0]]
+ else:
+ return self._base_actions[1:]
def exec_action(self, data: Any):
"""
diff --git a/archinstall/lib/menu/menu.py b/archinstall/lib/menu/menu.py
index 3a26f6e7..80982db0 100644
--- a/archinstall/lib/menu/menu.py
+++ b/archinstall/lib/menu/menu.py
@@ -1,5 +1,6 @@
from dataclasses import dataclass
from enum import Enum, auto
+from os import system
from typing import Dict, List, Union, Any, TYPE_CHECKING, Optional
from archinstall.lib.menu.simple_menu import TerminalMenu
@@ -57,7 +58,11 @@ class Menu(TerminalMenu):
header :Union[List[str],str] = None,
explode_on_interrupt :bool = False,
explode_warning :str = '',
- **kwargs
+ clear_screen: bool = True,
+ show_search_hint: bool = True,
+ cycle_cursor: bool = True,
+ clear_menu_on_exit: bool = True,
+ skip_empty_entries: bool = False
):
"""
Creates a new menu
@@ -153,8 +158,7 @@ class Menu(TerminalMenu):
if header:
if not isinstance(header,(list,tuple)):
header = [header]
- header = '\n'.join(header)
- menu_title += f'\n{header}\n'
+ menu_title += '\n'.join(header)
action_info = ''
if skip:
@@ -178,10 +182,6 @@ class Menu(TerminalMenu):
cursor = "> "
main_menu_cursor_style = ("fg_cyan", "bold")
main_menu_style = ("bg_blue", "fg_gray")
- # defaults that can be changed up the stack
- kwargs['clear_screen'] = kwargs.get('clear_screen',True)
- kwargs['show_search_hint'] = kwargs.get('show_search_hint',True)
- kwargs['cycle_cursor'] = kwargs.get('cycle_cursor',True)
super().__init__(
menu_entries=self._menu_options,
@@ -200,7 +200,11 @@ class Menu(TerminalMenu):
preview_title=preview_title,
explode_on_interrupt=self._explode_on_interrupt,
multi_select_select_on_accept=False,
- **kwargs,
+ clear_screen=clear_screen,
+ show_search_hint=show_search_hint,
+ cycle_cursor=cycle_cursor,
+ clear_menu_on_exit=clear_menu_on_exit,
+ skip_empty_entries=skip_empty_entries
)
def _show(self) -> MenuSelection:
@@ -238,6 +242,7 @@ class Menu(TerminalMenu):
return self.run()
if ret.type_ is not MenuSelectionType.Selection and not self._skip:
+ system('clear')
return self.run()
return ret