Coverage for src/hods/tui/config/columns.py: 100.00%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""hods - home directory synchronization.
3Copyright (C) 2016-2020 Mathias Stelzer <knoppo@rolln.de>
5hods is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
10hods is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
15You should have received a copy of the GNU General Public License
16along with this program. If not, see <http://www.gnu.org/licenses/>.
17"""
18import logging
20import urwid
22from hods.config.sources import GitRepository
23from hods.node import NotInTree
24from hods.tui.base.list import AppItemMixin, Item, ObjectItem, SortableItemListBox
25from hods.tui.base.view import ErrorWindow, View
26from hods.tui.base.widgets import FocusMap
27from hods.tui.config.edit import (
28 FeatureAddWindow,
29 FeatureEditWindow,
30 GitRepositoryAddWindow,
31 GitRepositoryEditWindow,
32 ServerDirectoryAddWindow,
33 ServerDirectoryEditWindow,
34 SourceFileAddWindow,
35)
36from hods.tui.config.tree import ConfigViewMixin, PathTreeBox, SourceTree, SourceTreeDecoration
38logger = logging.getLogger(__name__)
41class ConfigColumnsItem(ObjectItem):
42 """A list item to display a config column object."""
44 def __init__(self, *args, **kwargs):
45 """Initialize item."""
46 kwargs.setdefault('require_doubleclick', True)
47 super().__init__(*args, **kwargs)
49 def get_label(self):
50 """Return the objects name to display."""
51 return self.object.name
54class FeatureItem(AppItemMixin, ConfigColumnsItem):
55 """A list item to display a feature in a config column."""
57 def get_attr(self):
58 """Return a urwid attribute for the feature.
60 Show it as 'disabled' if its not installed.
61 """
62 if self.object.installed:
63 return 'feature'
64 return 'disabled'
66 def on_press(self, user_data=None):
67 """Callback to handle item selection.
69 Show the feature edit window.
70 """
71 FeatureEditWindow(self.app, self.object).show()
74class SourceItem(AppItemMixin, ConfigColumnsItem):
75 """A list item to display a source in a config column."""
77 def get_attr(self):
78 """Return a urwid attribute for the source."""
79 if not self.object.is_dependency_installed():
80 return 'error'
81 return 'source'
83 def on_press(self, user_data=None):
84 """Callback to handle item selection.
86 Show the according source edit window.
87 """
88 if isinstance(self.object, GitRepository):
89 GitRepositoryEditWindow(self.app, self.object).show()
90 else:
91 ServerDirectoryEditWindow(self.app, self.object).show()
94class SourceTreeBox(AppItemMixin, PathTreeBox):
95 """Box widget to display a source tree."""
97 pass
100class ConfigColumnsView(ConfigViewMixin, View, urwid.Columns):
101 """A view to display the configuration tree in 3 columns."""
103 def __init__(self, app, **kwargs):
104 """Initialize view.
106 :param app: `hods.tui.__main__.App`
107 :param kwargs: pass to parent
108 """
109 self.feature_listbox = SortableItemListBox(app, refresh=self.refresh, on_select=self.on_focus_feature_item)
110 self.source_listbox = SortableItemListBox(app, refresh=self.refresh, on_select=self.on_focus_source_item)
111 self._last_source = None
112 items = [
113 urwid.SolidFill(),
114 urwid.SolidFill(),
115 FocusMap(urwid.SolidFill(), 'body'),
116 ]
117 kwargs.setdefault('dividechars', 1)
118 self.treebox = None
119 super().__init__(app, items, **kwargs)
121 def keypress(self, size, key):
122 """Key event callback.
124 On pressing 'a' open the `SourceFileAddWindow`.
125 """
126 key = super().keypress(size, key)
127 if key == 'a':
128 self.show_add_files()
129 return key
131 def refresh(self):
132 """Refresh feature column."""
133 items = [FeatureItem(self.app, feature) for feature in self.app.config.tree.features]
134 items.append(Item('New Feature', 'feature', on_press=self.show_new_feature, require_doubleclick=True))
135 self.feature_listbox.set_items(items)
136 self.contents[0] = (
137 FocusMap(self.feature_listbox, 'body'),
138 self.options(urwid.GIVEN, self.feature_listbox.width))
139 super().refresh()
141 def on_focus_feature_item(self, item=None):
142 """Refresh source column when focusing a feature."""
143 if getattr(item, 'object', None) is None:
144 items = []
145 else:
146 feature = item.object
147 items = [SourceItem(self.app, source) for source in feature.sources]
148 items.append(Item('New rsync source', 'source', on_press=self.show_new_rsync_directory,
149 require_doubleclick=True))
150 if GitRepository.is_dependency_installed():
151 items.append(Item('New git source', 'source', on_press=self.show_new_git_repository,
152 require_doubleclick=True))
154 self.source_listbox.set_items(items)
155 self.contents[1] = (
156 FocusMap(self.source_listbox, 'body'),
157 self.options(urwid.GIVEN, self.source_listbox.width))
159 def on_focus_source_item(self, item=None):
160 """Refresh source file tree when focusing a source."""
161 if getattr(item, 'object', None) is None:
162 self.treebox = urwid.SolidFill()
163 self._last_source = None
164 else:
165 source = item.object
166 tree = SourceTree(self.app, source)
167 decorated_tree = SourceTreeDecoration(self.app, tree)
168 focus = self.get_treebox_focus()
169 self.treebox = SourceTreeBox(self.app, decorated_tree)
170 self.treebox.refresh()
172 # reset focus if the source didn't change
173 if source == self._last_source and focus is not None:
174 try:
175 new_focus = source.find(focus)
176 except NotInTree: # pragma: no cover
177 new_focus = None # file does not belong to source
178 else:
179 if new_focus is None: # file was deleted, focus parent
180 try:
181 new_focus = source.find(focus.parent)
182 except NotInTree: # pragma: no cover
183 new_focus = None # parent does not belong to source
185 if new_focus is not None:
186 self.treebox.set_focus(new_focus)
188 self._last_source = source
190 self.contents[2] = (urwid.AttrMap(self.treebox, 'body'), self.options())
192 def get_treebox_focus(self):
193 """Retrieve the focused item."""
194 if self._last_source is None:
195 return
196 return self.treebox.get_focus()[1]
198 def on_new_feature(self, feature):
199 """Callback to focus the newly created feature."""
200 index = self.app.config.tree.features.index(feature)
201 self.feature_listbox.focus_position = index
203 def on_new_source(self, source):
204 """Callback to focus the newly created source."""
205 feature = self.feature_listbox.selected.object
206 index = feature.sources.index(source)
207 self.source_listbox.focus_position = index
209 def show_add_files(self, user_data=None):
210 """Show window to add files to selected source directory."""
211 if self._last_source and self.treebox.selected:
212 from hods.node import FileNode
214 obj = self.treebox.selected
215 if isinstance(obj, FileNode):
216 obj = obj.parent
217 SourceFileAddWindow(self.app, obj).show()
218 else:
219 ErrorWindow(self.app, 'Select a source to add files to').show()
221 def show_new_feature(self, user_data=None):
222 """Show the new feature window."""
223 FeatureAddWindow(self.app, on_save=self.on_new_feature, defer_save=False).show()
225 def show_new_git_repository(self, user_data=None):
226 """Show the new git repository window."""
227 GitRepositoryAddWindow(self.app, self.feature_listbox.selected.object,
228 on_save=self.on_new_source, defer_save=False).show()
230 def show_new_rsync_directory(self, user_data=None):
231 """Show the new rsync directory window."""
232 ServerDirectoryAddWindow(self.app, self.feature_listbox.selected.object,
233 on_save=self.on_new_source, defer_save=False).show()