Hide keyboard shortcuts

Hot-keys 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. 

2 

3Copyright (C) 2016-2020 Mathias Stelzer <knoppo@rolln.de> 

4 

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. 

9 

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. 

14 

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 

19 

20import urwid 

21 

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 

37 

38logger = logging.getLogger(__name__) 

39 

40 

41class ConfigColumnsItem(ObjectItem): 

42 """A list item to display a config column object.""" 

43 

44 def __init__(self, *args, **kwargs): 

45 """Initialize item.""" 

46 kwargs.setdefault('require_doubleclick', True) 

47 super().__init__(*args, **kwargs) 

48 

49 def get_label(self): 

50 """Return the objects name to display.""" 

51 return self.object.name 

52 

53 

54class FeatureItem(AppItemMixin, ConfigColumnsItem): 

55 """A list item to display a feature in a config column.""" 

56 

57 def get_attr(self): 

58 """Return a urwid attribute for the feature. 

59 

60 Show it as 'disabled' if its not installed. 

61 """ 

62 if self.object.installed: 

63 return 'feature' 

64 return 'disabled' 

65 

66 def on_press(self, user_data=None): 

67 """Callback to handle item selection. 

68 

69 Show the feature edit window. 

70 """ 

71 FeatureEditWindow(self.app, self.object).show() 

72 

73 

74class SourceItem(AppItemMixin, ConfigColumnsItem): 

75 """A list item to display a source in a config column.""" 

76 

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' 

82 

83 def on_press(self, user_data=None): 

84 """Callback to handle item selection. 

85 

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() 

92 

93 

94class SourceTreeBox(AppItemMixin, PathTreeBox): 

95 """Box widget to display a source tree.""" 

96 

97 pass 

98 

99 

100class ConfigColumnsView(ConfigViewMixin, View, urwid.Columns): 

101 """A view to display the configuration tree in 3 columns.""" 

102 

103 def __init__(self, app, **kwargs): 

104 """Initialize view. 

105 

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) 

120 

121 def keypress(self, size, key): 

122 """Key event callback. 

123 

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 

130 

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() 

140 

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)) 

153 

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)) 

158 

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() 

171 

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 

184 

185 if new_focus is not None: 

186 self.treebox.set_focus(new_focus) 

187 

188 self._last_source = source 

189 

190 self.contents[2] = (urwid.AttrMap(self.treebox, 'body'), self.options()) 

191 

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] 

197 

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 

202 

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 

208 

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 

213 

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() 

220 

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() 

224 

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() 

229 

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()