ATLAS Offline Software
pad.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
2 
3 import curses
4 import logging
5 import sys
6 from typing import Dict, List, Set, Tuple
7 
8 from AthenaConfiguration.iconfTool.models.element import (
9  Element,
10  GroupingElement,
11 )
12 from AthenaConfiguration.iconfTool.models.structure import ComponentsStructure
13 
14 logger = logging.getLogger(__name__)
15 
16 
17 def 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 
24 class Pad:
25  def __init__(
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:
49  self.initialize_styles()
50  except Exception:
51  report()
52  self.draw_all_structures()
53  self.actual_y: int = 0
54  try:
55  self.initialize_cursor()
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()
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()
161  self.draw_all_structures()
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()
python.iconfTool.gui.pad.Pad.initialize_styles
None initialize_styles(self)
Definition: pad.py:60
python.iconfTool.gui.pad.report
def report()
Definition: pad.py:17
python.iconfTool.gui.pad.Pad.set_as_checked
None set_as_checked(self)
Definition: pad.py:347
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
python.iconfTool.gui.pad.Pad.reload_data
None reload_data(self, ComponentsStructure data_structure)
Definition: pad.py:74
python.iconfTool.gui.pad.Pad.redraw
None redraw(self, bool reinit_cursor=False)
Definition: pad.py:158
python.iconfTool.gui.pad.Pad.handle_event
None handle_event(self, int event)
Definition: pad.py:357
python.iconfTool.gui.pad.Pad.show_cursor
None show_cursor(self)
Definition: pad.py:226
python.iconfTool.gui.pad.Pad.collapse_all
def collapse_all(self)
Definition: pad.py:335
python.iconfTool.gui.pad.Pad.clear
None clear(self)
Definition: pad.py:154
python.iconfTool.gui.pad.Pad.actual_offset
actual_offset
Definition: pad.py:165
python.iconfTool.gui.pad.Pad.get_character
str get_character(self, int y, int x)
Definition: pad.py:199
python.iconfTool.gui.pad.Pad.move_cursor
None move_cursor(self, int number)
Definition: pad.py:205
python.iconfTool.gui.pad.Pad.structure
structure
Definition: pad.py:75
python.iconfTool.gui.pad.Pad.root_items_names
root_items_names
Definition: pad.py:77
python.iconfTool.gui.pad.Pad.structure_list
structure_list
Definition: pad.py:76
python.iconfTool.gui.pad.Pad.hide_cursor
Tuple[int, int] hide_cursor(self)
Definition: pad.py:213
python.iconfTool.gui.pad.Pad.collapse_children
None collapse_children(self, Element element)
Definition: pad.py:341
python.iconfTool.gui.pad.Pad.actual_y
actual_y
Definition: pad.py:87
python.iconfTool.gui.pad.Pad.refresh
None refresh(self)
Definition: pad.py:139
python.iconfTool.gui.pad.Pad.lines_empty
bool lines_empty(self)
Definition: pad.py:151
python.iconfTool.gui.pad.Pad.move_up
None move_up(self)
Definition: pad.py:286
python.iconfTool.gui.pad.Pad.__init__
None __init__(self, ComponentsStructure data_structure, int width, int height, int start_x=0)
Definition: pad.py:25
python.iconfTool.gui.pad.Pad.scroll_to_element
None scroll_to_element(self, int new_y)
Definition: pad.py:257
python.iconfTool.gui.pad.Pad.show_all
None show_all(self)
Definition: pad.py:195
python.iconfTool.gui.pad.Pad.pad
pad
Definition: pad.py:34
python.iconfTool.gui.pad.Pad.draw_structure
int draw_structure(self, Element structure, int start_y)
Definition: pad.py:101
python.iconfTool.gui.pad.Pad.expand_all
None expand_all(self, filter_comps=False)
Definition: pad.py:304
python.iconfTool.gui.pad.Pad.expand_children
None expand_children(self, Element element, filter_comps)
Definition: pad.py:310
python.iconfTool.gui.pad.Pad.expand
None expand(self)
Definition: pad.py:295
python.iconfTool.gui.pad.Pad.set_cursor
None set_cursor(self, int y, int x)
Definition: pad.py:239
python.iconfTool.gui.pad.Pad.styles
styles
Definition: pad.py:68
python.iconfTool.gui.pad.Pad.draw_all_structures
None draw_all_structures(self, start_y=0)
Definition: pad.py:130
python.iconfTool.gui.pad.Pad.collapse
None collapse(self)
Definition: pad.py:327
python.iconfTool.gui.pad.Pad.lines
lines
Definition: pad.py:131
python.iconfTool.gui.pad.Pad.filter
None filter(self, str text)
Definition: pad.py:182
python.iconfTool.gui.pad.Pad.initialize_cursor
None initialize_cursor(self)
Definition: pad.py:81
python.iconfTool.gui.pad.Pad
Definition: pad.py:24
python.iconfTool.gui.pad.Pad.get_index_by_name
int get_index_by_name(self, str name)
Definition: pad.py:273
python.iconfTool.gui.pad.Pad.move_down
None move_down(self)
Definition: pad.py:279
python.iconfTool.gui.pad.Pad.search_text
search_text
Definition: pad.py:183
python.iconfTool.gui.pad.Pad.scroll
None scroll(self, int number)
Definition: pad.py:253