move filesystem summary to separate view
This commit is contained in:
parent
de80a06f47
commit
24788c5c9a
|
@ -20,8 +20,20 @@ configuration.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from urwid import Text
|
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
from urwid import (
|
||||||
|
AttrMap,
|
||||||
|
CompositeCanvas,
|
||||||
|
connect_signal,
|
||||||
|
Text,
|
||||||
|
WidgetDecoration,
|
||||||
|
)
|
||||||
|
|
||||||
|
from subiquitycore.ui.actionmenu import (
|
||||||
|
ActionMenu,
|
||||||
|
)
|
||||||
from subiquitycore.ui.buttons import (
|
from subiquitycore.ui.buttons import (
|
||||||
back_btn,
|
back_btn,
|
||||||
cancel_btn,
|
cancel_btn,
|
||||||
|
@ -30,8 +42,14 @@ from subiquitycore.ui.buttons import (
|
||||||
menu_btn,
|
menu_btn,
|
||||||
reset_btn,
|
reset_btn,
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.container import Columns, ListBox, Pile
|
from subiquitycore.ui.container import (
|
||||||
|
Columns,
|
||||||
|
ListBox,
|
||||||
|
Pile,
|
||||||
|
WidgetWrap,
|
||||||
|
)
|
||||||
from subiquitycore.ui.stretchy import Stretchy
|
from subiquitycore.ui.stretchy import Stretchy
|
||||||
|
from subiquitycore.ui.table import ColSpec, Table, TableRow
|
||||||
from subiquitycore.ui.utils import button_pile, Color, Padding
|
from subiquitycore.ui.utils import button_pile, Color, Padding
|
||||||
from subiquitycore.view import BaseView
|
from subiquitycore.view import BaseView
|
||||||
|
|
||||||
|
@ -75,6 +93,156 @@ class FilesystemConfirmation(Stretchy):
|
||||||
self.parent.remove_overlay()
|
self.parent.remove_overlay()
|
||||||
|
|
||||||
|
|
||||||
|
class CursorOverride(WidgetDecoration):
|
||||||
|
"""Decoration to override where the cursor goes when a widget is focused.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, w, cursor_x=0):
|
||||||
|
super().__init__(w)
|
||||||
|
self.cursor_x = cursor_x
|
||||||
|
|
||||||
|
def get_cursor_coords(self, size):
|
||||||
|
return self.cursor_x, 0
|
||||||
|
|
||||||
|
def rows(self, size, focus):
|
||||||
|
return self._original_widget.rows(size, focus)
|
||||||
|
|
||||||
|
def keypress(self, size, focus):
|
||||||
|
return self._original_widget.keypress(size, focus)
|
||||||
|
|
||||||
|
def render(self, size, focus=False):
|
||||||
|
c = self._original_widget.render(size, focus)
|
||||||
|
if focus:
|
||||||
|
# create a new canvas so we can add a cursor
|
||||||
|
c = CompositeCanvas(c)
|
||||||
|
c.cursor = self.get_cursor_coords(size)
|
||||||
|
return c
|
||||||
|
|
||||||
|
|
||||||
|
def add_menu_row_focus_behaviour(menu, row, attr_map, focus_map, cursor_x=0):
|
||||||
|
"""Configure focus behaviour of row (which contains menu)
|
||||||
|
|
||||||
|
The desired behaviour is that:
|
||||||
|
|
||||||
|
1) The cursor appears at the left of the row rather than where the
|
||||||
|
menu is.
|
||||||
|
2) The row is highlighted when focused and retains that focus even
|
||||||
|
when the popup is open.
|
||||||
|
"""
|
||||||
|
am = AttrMap(CursorOverride(row, cursor_x=cursor_x), attr_map, focus_map)
|
||||||
|
connect_signal(menu, 'open', lambda menu: am.set_attr_map(focus_map))
|
||||||
|
connect_signal(menu, 'close', lambda menu: am.set_attr_map(attr_map))
|
||||||
|
return am
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class MountInfo:
|
||||||
|
mount = attr.ib(default=None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self.mount.path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def split_path(self):
|
||||||
|
return self.mount.path.split('/')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
return humanize_size(self.mount.device.volume.size)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fstype(self):
|
||||||
|
return self.mount.device.fstype
|
||||||
|
|
||||||
|
@property
|
||||||
|
def desc(self):
|
||||||
|
return self.mount.device.volume.desc()
|
||||||
|
|
||||||
|
def startswith(self, other):
|
||||||
|
i = 0
|
||||||
|
for a, b in zip(self.split_path, other.split_path):
|
||||||
|
if a != b:
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
return i >= len(other.split_path)
|
||||||
|
|
||||||
|
|
||||||
|
class MountList(WidgetWrap):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.table = Table([], spacing=2, colspecs={
|
||||||
|
0: ColSpec(can_shrink=True),
|
||||||
|
1: ColSpec(min_width=9),
|
||||||
|
})
|
||||||
|
self._no_mounts_content = Color.info_minor(
|
||||||
|
Text(_("No disks or partitions mounted.")))
|
||||||
|
super().__init__(self.table)
|
||||||
|
self.refresh_model_inputs()
|
||||||
|
|
||||||
|
def _mount_action(self, sender, action, mount):
|
||||||
|
log.debug('_mount_action %s %s', action, mount)
|
||||||
|
if action == 'unmount':
|
||||||
|
self.parent.controller.delete_mount(mount)
|
||||||
|
self.parent.refresh_model_inputs()
|
||||||
|
|
||||||
|
def refresh_model_inputs(self):
|
||||||
|
mountinfos = [
|
||||||
|
MountInfo(mount=m)
|
||||||
|
for m in sorted(
|
||||||
|
self.parent.model.all_mounts(), key=lambda m: m.path)
|
||||||
|
]
|
||||||
|
if len(mountinfos) == 0:
|
||||||
|
self.table.set_contents([])
|
||||||
|
self._w = self._no_mounts_content
|
||||||
|
return
|
||||||
|
self._w = self.table
|
||||||
|
log.debug('FileSystemView: building mount list')
|
||||||
|
|
||||||
|
rows = [TableRow([
|
||||||
|
Text(_("MOUNT POINT")),
|
||||||
|
Text(_("SIZE"), align='center'),
|
||||||
|
Text(_("TYPE")),
|
||||||
|
Text(_("DEVICE TYPE")),
|
||||||
|
])]
|
||||||
|
|
||||||
|
for i, mi in enumerate(mountinfos):
|
||||||
|
path_markup = mi.path
|
||||||
|
for j in range(i-1, -1, -1):
|
||||||
|
mi2 = mountinfos[j]
|
||||||
|
if mi.startswith(mi2):
|
||||||
|
part1 = "/".join(mi.split_path[:len(mi2.split_path)])
|
||||||
|
part2 = "/".join(
|
||||||
|
[''] + mi.split_path[len(mi2.split_path):])
|
||||||
|
path_markup = [('info_minor', part1), part2]
|
||||||
|
break
|
||||||
|
if j == 0 and mi2.split_path == ['', '']:
|
||||||
|
path_markup = [
|
||||||
|
('info_minor', "/"),
|
||||||
|
"/".join(mi.split_path[1:]),
|
||||||
|
]
|
||||||
|
actions = [(_("Unmount"), mi.mount.can_delete(), 'unmount')]
|
||||||
|
menu = ActionMenu(actions)
|
||||||
|
connect_signal(menu, 'action', self._mount_action, mi.mount)
|
||||||
|
row = TableRow([
|
||||||
|
Text(path_markup),
|
||||||
|
Text(mi.size, align='right'),
|
||||||
|
Text(mi.fstype),
|
||||||
|
Text(mi.desc),
|
||||||
|
menu,
|
||||||
|
])
|
||||||
|
row = add_menu_row_focus_behaviour(
|
||||||
|
menu,
|
||||||
|
row,
|
||||||
|
'menu_button',
|
||||||
|
{None: 'menu_button focus', 'info_minor': 'menu_button focus'})
|
||||||
|
rows.append(row)
|
||||||
|
self.table.set_contents(rows)
|
||||||
|
if self.table._w.focus_position >= len(rows):
|
||||||
|
self.table._w.focus_position = len(rows) - 1
|
||||||
|
|
||||||
|
|
||||||
class FilesystemView(BaseView):
|
class FilesystemView(BaseView):
|
||||||
title = _("Filesystem setup")
|
title = _("Filesystem setup")
|
||||||
footer = _("Select available disks to format and mount")
|
footer = _("Select available disks to format and mount")
|
||||||
|
@ -84,10 +252,11 @@ class FilesystemView(BaseView):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.items = []
|
self.items = []
|
||||||
|
self.mount_list = MountList(self)
|
||||||
body = [
|
body = [
|
||||||
Text(_("FILE SYSTEM SUMMARY")),
|
Text(_("FILE SYSTEM SUMMARY")),
|
||||||
Text(""),
|
Text(""),
|
||||||
Padding.push_4(self._build_filesystem_list()),
|
self.mount_list,
|
||||||
Text(""),
|
Text(""),
|
||||||
Text(_("AVAILABLE DEVICES")),
|
Text(_("AVAILABLE DEVICES")),
|
||||||
Text(""),
|
Text(""),
|
||||||
|
@ -108,49 +277,6 @@ class FilesystemView(BaseView):
|
||||||
super().__init__(self.frame)
|
super().__init__(self.frame)
|
||||||
log.debug('FileSystemView init complete()')
|
log.debug('FileSystemView init complete()')
|
||||||
|
|
||||||
def _build_used_disks(self):
|
|
||||||
log.debug('FileSystemView: building used disks')
|
|
||||||
return Color.info_minor(
|
|
||||||
Text("No disks have been used to create a constructed disk."))
|
|
||||||
|
|
||||||
def _build_filesystem_list(self):
|
|
||||||
log.debug('FileSystemView: building part list')
|
|
||||||
cols = []
|
|
||||||
mount_point_text = _("MOUNT POINT")
|
|
||||||
longest_path = len(mount_point_text)
|
|
||||||
for m in sorted(self.model._mounts, key=lambda m: m.path):
|
|
||||||
path = m.path
|
|
||||||
longest_path = max(longest_path, len(path))
|
|
||||||
for p, *dummy in reversed(cols):
|
|
||||||
if path.startswith(p):
|
|
||||||
path = [('info_minor', p), path[len(p):]]
|
|
||||||
break
|
|
||||||
cols.append((m.path, path, humanize_size(m.device.volume.size),
|
|
||||||
m.device.fstype, m.device.volume.desc()))
|
|
||||||
for fs in self.model._filesystems:
|
|
||||||
if fs.fstype == 'swap':
|
|
||||||
cols.append((None, _('SWAP'), humanize_size(fs.volume.size),
|
|
||||||
fs.fstype, fs.volume.desc()))
|
|
||||||
|
|
||||||
if len(cols) == 0:
|
|
||||||
return Pile([Color.info_minor(
|
|
||||||
Text(_("No disks or partitions mounted.")))])
|
|
||||||
size_text = _("SIZE")
|
|
||||||
type_text = _("TYPE")
|
|
||||||
size_width = max(len(size_text), 9)
|
|
||||||
type_width = max(len(type_text), self.model.longest_fs_name)
|
|
||||||
cols.insert(0, (None, mount_point_text, size_text, type_text,
|
|
||||||
_("DEVICE TYPE")))
|
|
||||||
pl = []
|
|
||||||
for dummy, a, b, c, d in cols:
|
|
||||||
if b == "SIZE":
|
|
||||||
b = Text(b, align='center')
|
|
||||||
else:
|
|
||||||
b = Text(b, align='right')
|
|
||||||
pl.append(Columns([(longest_path, Text(a)), (size_width, b),
|
|
||||||
(type_width, Text(c)), Text(d)], 4))
|
|
||||||
return Pile(pl)
|
|
||||||
|
|
||||||
def _build_buttons(self):
|
def _build_buttons(self):
|
||||||
log.debug('FileSystemView: building buttons')
|
log.debug('FileSystemView: building buttons')
|
||||||
buttons = []
|
buttons = []
|
||||||
|
@ -167,6 +293,9 @@ class FilesystemView(BaseView):
|
||||||
|
|
||||||
return button_pile(buttons)
|
return button_pile(buttons)
|
||||||
|
|
||||||
|
def refresh_model_inputs(self):
|
||||||
|
self.mount_list.refresh_model_inputs()
|
||||||
|
|
||||||
def _build_available_inputs(self):
|
def _build_available_inputs(self):
|
||||||
r = []
|
r = []
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue