ATLAS Offline Software
Loading...
Searching...
No Matches
pad.py
Go to the documentation of this file.
1# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
2
3import curses
4import logging
5import sys
6from typing import Dict, List, Set, Tuple
7
8from AthenaConfiguration.iconfTool.models.element import (
9 Element,
10 GroupingElement,
11)
12from AthenaConfiguration.iconfTool.models.structure import ComponentsStructure
13
14logger = logging.getLogger(__name__)
15
16
17def report():
18 logger.error(
19 "Can not init textGUI, probably wrong terminal. Try setting export TERM=xterm-256color"
20 )
21 sys.exit(-1)
22
23
24class Pad:
26 self,
27 data_structure: ComponentsStructure,
28 width: int,
29 height: int,
30 start_x: int = 0,
31 ) -> None:
32 self.structure: ComponentsStructure = data_structure
33 self.structure_list: List[Element] = self.structure.get_list()
34 self.root_items_names: Set = {
35 element.name for element in self.structure_list
36 }
37 self.width: int = width
38 self.start_x: int = start_x
39 self.MAX_PAD_HEIGHT: int = 32000
40 self.pad = curses.newpad(self.MAX_PAD_HEIGHT, width)
41 self.actual_offset: int = 0
42 self.pad_height: int = height - 1
43
44 self.mark_character: str = " "
45 self.styles: Dict
46 self.lines: List[Element] = []
47 self.search_text: str = ""
48 try:
50 except Exception:
51 report()
53 self.actual_y: int = 0
54 try:
56 except Exception:
57 report()
58 self.refresh()
59
60 def initialize_styles(self) -> None:
61 curses.init_pair(
62 1, curses.COLOR_BLACK, curses.COLOR_WHITE
63 ) # Sets up color pair
64 highlighted = curses.color_pair(1) # highlighted menu option
65 normal = curses.A_NORMAL # non-highlighted menu option
66 curses.init_pair(3, curses.COLOR_GREEN, curses.COLOR_BLACK)
67 marked = curses.color_pair(3)
68 self.styles = {
69 "normal": normal,
70 "highlighted": highlighted,
71 "marked": marked,
72 }
73
74 def reload_data(self, data_structure: ComponentsStructure) -> None:
75 self.structure = data_structure
76 self.structure_list = self.structure.get_list()
77 self.root_items_names = {
78 element.name for element in self.structure_list
79 }
80
81 def initialize_cursor(self) -> None:
82 # Highlight cursor on initial position
83 if self.lines_empty():
84 return
85 try:
86 if self.actual_y >= len(self.lines):
87 self.actual_y = 0
88 element = self.lines[self.actual_y]
89 curses.setsyx(self.actual_y, element.x_pos + 1)
90 self.pad.addch(
91 curses.getsyx()[0],
92 curses.getsyx()[1],
93 element.get_mark_character(),
94 self.styles["highlighted"],
95 )
96 curses.curs_set(0)
97 except IndexError:
98 # Handle situation when self.lines list is empty
99 pass
100
101 def draw_structure(self, structure: Element, start_y: int) -> int:
102 """start_y - starting vertical position where elements will be drawn"""
103 style = self.styles["marked" if structure.is_marked() else "normal"]
104 logger.debug(
105 f"Adding str: x:{structure.x_pos}, y:{start_y}/{self.MAX_PAD_HEIGHT}"
106 )
107 try:
108 self.pad.addstr(
109 start_y,
110 structure.x_pos,
111 structure.get_line(),
112 style,
113 )
114 except Exception as e:
115 logger.error(
116 f"Error {e} loading str y:{start_y}, x: {structure.x_pos}"
117 )
118 self.lines.insert(start_y, structure)
119 start_y += 1
120 if isinstance(structure, GroupingElement) and structure.show_children:
121 for child in structure.children:
122 if start_y < self.MAX_PAD_HEIGHT:
123 start_y = self.draw_structure(child, start_y)
124 else:
125 logger.error(
126 f"Too many structures: {start_y}. Max count: {self.MAX_PAD_HEIGHT}"
127 )
128 return start_y
129
130 def draw_all_structures(self, start_y=0) -> None:
131 self.lines = []
132 start_y = 0
133 for i in self.structure_list:
134 if start_y >= self.MAX_PAD_HEIGHT:
135 break
136 start_y = self.draw_structure(i, start_y)
137 self.refresh()
138
139 def refresh(self) -> None:
140 self.pad.refresh(
141 self.actual_offset,
142 0,
143 0,
144 self.start_x,
145 self.pad_height,
146 self.width + self.start_x - 2,
147 )
148 cursor_pos = curses.getsyx()
149 curses.setsyx(cursor_pos[0], int(cursor_pos[1] - self.start_x))
150
151 def lines_empty(self) -> bool:
152 return len(self.lines) == 0
153
154 def clear(self) -> None:
155 self.pad.clear()
156 self.refresh()
157
158 def redraw(self, reinit_cursor: bool = False) -> None:
159 cursor_pos = curses.getsyx()
160 self.pad.clear()
162 if self.lines_empty():
163 return
164 if reinit_cursor or cursor_pos[1] == 0:
165 self.actual_offset = 0
166 self.actual_y = 0
167 self.initialize_cursor()
168 else:
169 logger.debug(
170 f"y: {self.actual_y}, lines length: {len(self.lines)}"
171 )
172 element = self.lines[self.actual_y]
173 self.pad.move(self.actual_y, cursor_pos[1])
174 self.pad.addch(
175 self.actual_y,
176 cursor_pos[1] - 1,
177 element.get_mark_character(),
178 self.styles["highlighted"],
179 )
180 self.refresh()
181
182 def filter(self, text: str) -> None:
183 self.search_text = text if text else ""
184 self.collapse_all()
185
186 if len(self.search_text) > 0:
187 self.structure.filter_by_text(text)
188 if self.structure.filter.comps_to_save:
189 self.expand_all(filter_comps=True)
190 for element in self.lines:
191 if text in element.get_name() and not element.is_checked():
192 element.set_as_checked()
193 self.redraw()
194
195 def show_all(self) -> None:
196 self.structure_list = self.structure.get_list()
197 self.redraw(True)
198
199 def get_character(self, y: int, x: int) -> str:
200 current_character = chr(
201 int(self.pad.inch(y, x)) & 0xFF
202 ) # get only the first 8 bits of a number
203 return current_character
204
205 def move_cursor(self, number: int) -> None:
206 cursor_pos = self.hide_cursor()
207 self.actual_y += number
208 if self.actual_y < len(self.lines):
209 self.set_cursor(
210 cursor_pos[0] + number, self.lines[self.actual_y].x_pos + 1
211 )
212
213 def hide_cursor(self) -> Tuple[int, int]:
214 cursor_pos = curses.getsyx()
215 current_character = self.get_character(
216 cursor_pos[0] + self.actual_offset, cursor_pos[1] - 1
217 )
218 self.pad.addch(
219 cursor_pos[0] + self.actual_offset,
220 cursor_pos[1] - 1,
221 current_character,
222 self.styles["normal"],
223 )
224 return cursor_pos
225
226 def show_cursor(self) -> None:
227 cursor_pos = curses.getsyx()
228 current_character = self.get_character(
229 cursor_pos[0] + self.actual_offset, cursor_pos[1] - 1
230 )
231 self.pad.addch(
232 cursor_pos[0] + self.actual_offset,
233 cursor_pos[1] - 1,
234 current_character,
235 self.styles["highlighted"],
236 )
237 self.refresh()
238
239 def set_cursor(self, y: int, x: int) -> None:
240 curses.setsyx(y, x)
241 cursor_pos = curses.getsyx()
242 current_character = self.get_character(
243 cursor_pos[0] + self.actual_offset, cursor_pos[1]
244 )
245 self.pad.addch(
246 cursor_pos[0] + self.actual_offset,
247 cursor_pos[1],
248 current_character,
249 self.styles["highlighted"],
250 )
251 self.refresh()
252
253 def scroll(self, number: int) -> None:
254 self.actual_offset += number
255 self.refresh()
256
257 def scroll_to_element(self, new_y: int) -> None:
258 self.actual_y = new_y
259 self.hide_cursor()
260 # Handle the situation when element is above current view
261 if new_y < self.actual_offset:
262 self.actual_offset = new_y - 1
263 if self.actual_offset < 0:
264 self.actual_offset = 0
265 elif new_y > self.actual_offset + self.pad_height:
266 self.actual_offset = new_y - 1
267 if self.actual_offset + self.pad_height > len(self.lines):
268 self.actual_offset = len(self.lines) - self.pad_height
269 self.set_cursor(
270 new_y - self.actual_offset, self.lines[new_y].x_pos + 1
271 )
272
273 def get_index_by_name(self, name: str) -> int:
274 for index, element in enumerate(self.lines):
275 if element.parent is None and element.name == name:
276 return index
277 return -1
278
279 def move_down(self) -> None:
280 if self.actual_y < len(self.lines) - 1:
281 self.move_cursor(1)
282 cursor_pos = curses.getsyx()
283 if cursor_pos[0] > self.pad_height - 4:
284 self.scroll(1)
285
286 def move_up(self) -> None:
287 if len(self.lines) == 0:
288 return
289 if self.actual_y >= 1:
290 self.move_cursor(-1)
291 cursor_pos = curses.getsyx()
292 if cursor_pos[0] < 4 and self.actual_offset > 0:
293 self.scroll(-1)
294
295 def expand(self) -> None:
296 if self.actual_y >= len(self.lines):
297 return
298 element = self.lines[self.actual_y]
299 if isinstance(element, GroupingElement):
300 self.structure.replace_references(element)
301 element.show_children = True
302 self.redraw()
303
304 def expand_all(self, filter_comps=False) -> None:
305 for element in self.structure_list:
306 self.expand_children(element, filter_comps)
307 self.structure.filter.comps_to_save = []
308 self.redraw()
309
310 def expand_children(self, element: Element, filter_comps) -> None:
311 if (
312 filter_comps
313 and element.get_reference_name()
314 not in self.structure.filter.comps_to_save
315 and element.get_reference_name() in self.structure.roots_dict
316 ):
317 logger.debug(
318 f"Skipped expanding for: {element.get_reference_name()}"
319 )
320 return
321 if isinstance(element, GroupingElement):
322 self.structure.replace_references(element)
323 element.show_children = True
324 for child in element.children:
325 self.expand_children(child, filter_comps)
326
327 def collapse(self) -> None:
328 if self.actual_y >= len(self.lines):
329 return
330 element = self.lines[self.actual_y]
331 if isinstance(element, GroupingElement):
332 element.show_children = False
333 self.redraw()
334
335 def collapse_all(self):
336 for element in self.structure_list:
337 self.collapse_children(element)
338 self.actual_y = 0
339 self.redraw()
340
341 def collapse_children(self, element: Element) -> None:
342 if isinstance(element, GroupingElement):
343 element.show_children = False
344 for child in element.children:
345 self.collapse_children(child)
346
347 def set_as_checked(self) -> None:
348 element = self.lines[self.actual_y]
349 if element.checked:
350 element.set_as_unchecked()
351 self.structure.remove_from_checked(element)
352 else:
353 element.set_as_checked()
354 self.structure.add_to_checked(element)
355 self.redraw()
356
357 def handle_event(self, event: int) -> None:
358 if event == curses.KEY_DOWN:
359 self.move_down()
360 elif event == curses.KEY_UP:
361 self.move_up()
362 elif event == curses.KEY_RIGHT:
363 self.expand()
364 elif event == curses.KEY_LEFT:
365 self.collapse()
366 elif event == ord(" "):
367 self.set_as_checked()
None move_cursor(self, int number)
Definition pad.py:205
None set_cursor(self, int y, int x)
Definition pad.py:239
None __init__(self, ComponentsStructure data_structure, int width, int height, int start_x=0)
Definition pad.py:31
Tuple[int, int] hide_cursor(self)
Definition pad.py:213
None redraw(self, bool reinit_cursor=False)
Definition pad.py:158
int draw_structure(self, Element structure, int start_y)
Definition pad.py:101
None collapse_children(self, Element element)
Definition pad.py:341
None scroll_to_element(self, int new_y)
Definition pad.py:257
None expand_children(self, Element element, filter_comps)
Definition pad.py:310
None set_as_checked(self)
Definition pad.py:347
List[Element] structure_list
Definition pad.py:33
ComponentsStructure structure
Definition pad.py:32
None reload_data(self, ComponentsStructure data_structure)
Definition pad.py:74
int get_index_by_name(self, str name)
Definition pad.py:273
None initialize_cursor(self)
Definition pad.py:81
None filter(self, str text)
Definition pad.py:182
None handle_event(self, int event)
Definition pad.py:357
str get_character(self, int y, int x)
Definition pad.py:199
None draw_all_structures(self, start_y=0)
Definition pad.py:130
None initialize_styles(self)
Definition pad.py:60
None scroll(self, int number)
Definition pad.py:253
None expand_all(self, filter_comps=False)
Definition pad.py:304