listpick 0.1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- list_picker/__init__.py +0 -0
- list_picker/list_picker_app.py +2076 -0
- list_picker/ui/__init__.py +0 -0
- list_picker/ui/help_screen.py +91 -0
- list_picker/ui/input_field.py +165 -0
- list_picker/ui/keys.py +228 -0
- list_picker/ui/list_picker_colours.py +258 -0
- list_picker/utils/__init__.py +0 -0
- list_picker/utils/clipboard_operations.py +33 -0
- list_picker/utils/dump.py +78 -0
- list_picker/utils/filtering.py +32 -0
- list_picker/utils/generate_data.py +74 -0
- list_picker/utils/options_selectors.py +67 -0
- list_picker/utils/search_and_filter_utils.py +79 -0
- list_picker/utils/searching.py +76 -0
- list_picker/utils/sorting.py +120 -0
- list_picker/utils/table_to_list_of_lists.py +188 -0
- list_picker/utils/utils.py +251 -0
- listpick-0.1.4.0.dist-info/METADATA +186 -0
- listpick-0.1.4.0.dist-info/RECORD +22 -0
- listpick-0.1.4.0.dist-info/WHEEL +5 -0
- listpick-0.1.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2076 @@
|
|
1
|
+
#!/bin/python
|
2
|
+
|
3
|
+
import curses
|
4
|
+
import re
|
5
|
+
import os
|
6
|
+
import subprocess
|
7
|
+
import argparse
|
8
|
+
import time
|
9
|
+
from wcwidth import wcswidth
|
10
|
+
from typing import Callable, Optional, Tuple
|
11
|
+
|
12
|
+
from list_picker.ui.list_picker_colours import get_colours, get_help_colours, get_notification_colours, get_theme_count
|
13
|
+
from list_picker.utils.options_selectors import default_option_input, output_file_option_selector, default_option_selector
|
14
|
+
from list_picker.utils.table_to_list_of_lists import *
|
15
|
+
from list_picker.utils.utils import *
|
16
|
+
from list_picker.utils.sorting import *
|
17
|
+
from list_picker.utils.filtering import *
|
18
|
+
from list_picker.ui.input_field import *
|
19
|
+
from list_picker.utils.clipboard_operations import *
|
20
|
+
from list_picker.utils.searching import search
|
21
|
+
from list_picker.ui.help_screen import help_lines
|
22
|
+
from list_picker.ui.keys import list_picker_keys, notification_keys, options_keys, help_keys
|
23
|
+
from list_picker.utils.generate_data import generate_list_picker_data
|
24
|
+
from list_picker.utils.dump import dump_state, load_state, dump_data
|
25
|
+
|
26
|
+
try:
|
27
|
+
from tmp.data_stuff import test_items, test_highlights, test_header
|
28
|
+
except:
|
29
|
+
test_items, test_highlights, test_header = [], [], []
|
30
|
+
|
31
|
+
COLOURS_SET = False
|
32
|
+
help_colours, notification_colours = {}, {}
|
33
|
+
|
34
|
+
|
35
|
+
class Picker:
|
36
|
+
def __init__(self,
|
37
|
+
stdscr: curses.window,
|
38
|
+
items: list[list[str]] = [],
|
39
|
+
cursor_pos: int = 0,
|
40
|
+
colours: dict = get_colours(0),
|
41
|
+
colour_theme_number: int = 0,
|
42
|
+
max_selected: int = -1,
|
43
|
+
top_gap: int =0,
|
44
|
+
title: str ="List Picker",
|
45
|
+
header: list =[],
|
46
|
+
max_column_width: int =70,
|
47
|
+
clear_on_start: bool = False,
|
48
|
+
|
49
|
+
auto_refresh: bool =False,
|
50
|
+
timer: float = 5,
|
51
|
+
|
52
|
+
get_new_data: bool =False, # Whether we can get new data
|
53
|
+
refresh_function: Optional[Callable] = lambda: [], # The function with which we get new data
|
54
|
+
get_data_startup: bool =False, # Whether we should get data at statrup
|
55
|
+
track_entries_upon_refresh: bool = True,
|
56
|
+
id_column: int = 0,
|
57
|
+
|
58
|
+
unselectable_indices: list =[],
|
59
|
+
highlights: list =[],
|
60
|
+
highlights_hide: bool =False,
|
61
|
+
number_columns: bool =True,
|
62
|
+
column_widths: list = [],
|
63
|
+
column_indices: list = [],
|
64
|
+
|
65
|
+
|
66
|
+
current_row : int = 0,
|
67
|
+
current_page : int = 0,
|
68
|
+
is_selecting : bool = False,
|
69
|
+
is_deselecting : int = False,
|
70
|
+
start_selection: int = -1,
|
71
|
+
end_selection: int = -1,
|
72
|
+
user_opts : str = "",
|
73
|
+
user_settings : str = "",
|
74
|
+
separator : str = " ",
|
75
|
+
search_query : str = "",
|
76
|
+
search_count : int = 0,
|
77
|
+
search_index : int = 0,
|
78
|
+
filter_query : str = "",
|
79
|
+
hidden_columns: list = [],
|
80
|
+
indexed_items: list[Tuple[int, list[str]]] = [],
|
81
|
+
scroll_bar : int = True,
|
82
|
+
|
83
|
+
selections: dict = {},
|
84
|
+
highlight_full_row: bool =False,
|
85
|
+
|
86
|
+
items_per_page : int = -1,
|
87
|
+
sort_method : int = 0,
|
88
|
+
sort_reverse: list[bool] = [False],
|
89
|
+
sort_column : int = 0,
|
90
|
+
columns_sort_method: list[int] = [0],
|
91
|
+
key_chain: str = "",
|
92
|
+
last_key: Optional[str] = None,
|
93
|
+
|
94
|
+
paginate: bool =False,
|
95
|
+
cancel_is_back: bool = False,
|
96
|
+
mode_index: int =0,
|
97
|
+
modes: list[dict] = [{}],
|
98
|
+
display_modes: bool =False,
|
99
|
+
require_option: list=[],
|
100
|
+
option_functions: list[Callable[..., Tuple[bool, str]]] = [],
|
101
|
+
disabled_keys: list=[],
|
102
|
+
|
103
|
+
show_footer: bool =True,
|
104
|
+
footer_string: str="",
|
105
|
+
footer_string_auto_refresh: bool=False,
|
106
|
+
footer_string_refresh_function: Optional[Callable] = None,
|
107
|
+
footer_timer=1,
|
108
|
+
|
109
|
+
colours_start: int =0,
|
110
|
+
colours_end: int =-1,
|
111
|
+
key_remappings: dict = {},
|
112
|
+
keys_dict:dict = list_picker_keys,
|
113
|
+
display_infobox : bool = False,
|
114
|
+
infobox_items: list[list[str]] = [],
|
115
|
+
infobox_title: str = "",
|
116
|
+
display_only: bool = False,
|
117
|
+
|
118
|
+
editable_columns: list[int] = [],
|
119
|
+
|
120
|
+
centre_in_terminal: bool = False,
|
121
|
+
centre_in_terminal_vertical: bool = False,
|
122
|
+
centre_in_cols: bool = False,
|
123
|
+
|
124
|
+
startup_notification:str = "",
|
125
|
+
):
|
126
|
+
self.stdscr = stdscr
|
127
|
+
self.items = items
|
128
|
+
self.cursor_pos = cursor_pos
|
129
|
+
self.colours = colours
|
130
|
+
self.colour_theme_number = colour_theme_number
|
131
|
+
self.max_selected = max_selected
|
132
|
+
self.top_gap = top_gap
|
133
|
+
self.title = title
|
134
|
+
self.header = header
|
135
|
+
self.max_column_width = max_column_width
|
136
|
+
self.clear_on_start = clear_on_start
|
137
|
+
|
138
|
+
self.auto_refresh = auto_refresh
|
139
|
+
self.timer = timer
|
140
|
+
|
141
|
+
self.get_new_data = get_new_data
|
142
|
+
self.refresh_function = refresh_function
|
143
|
+
self.get_data_startup = get_data_startup
|
144
|
+
self.track_entries_upon_refresh = track_entries_upon_refresh
|
145
|
+
self.id_column = id_column
|
146
|
+
|
147
|
+
self.unselectable_indices = unselectable_indices
|
148
|
+
self.highlights = highlights
|
149
|
+
self.highlights_hide = highlights_hide
|
150
|
+
self.number_columns = number_columns
|
151
|
+
self.column_widths, = [],
|
152
|
+
self.column_indices, = [],
|
153
|
+
|
154
|
+
|
155
|
+
self.current_row = current_row
|
156
|
+
self.current_page = current_page
|
157
|
+
self.is_selecting = is_selecting
|
158
|
+
self.is_deselecting = is_deselecting
|
159
|
+
self.start_selection = start_selection
|
160
|
+
self.end_selection = end_selection
|
161
|
+
self.user_opts = user_opts
|
162
|
+
self.user_settings = user_settings
|
163
|
+
self.separator = separator
|
164
|
+
self.search_query = search_query
|
165
|
+
self.search_count = search_count
|
166
|
+
self.search_index = search_index
|
167
|
+
self.filter_query = filter_query
|
168
|
+
self.hidden_columns = hidden_columns
|
169
|
+
self.indexed_items = indexed_items
|
170
|
+
self.scroll_bar = scroll_bar
|
171
|
+
|
172
|
+
self.selections = selections
|
173
|
+
self.highlight_full_row = highlight_full_row
|
174
|
+
|
175
|
+
self.items_per_page = items_per_page
|
176
|
+
self.sort_method = sort_method
|
177
|
+
self.sort_reverse = sort_reverse
|
178
|
+
self.sort_column = sort_column
|
179
|
+
self.columns_sort_method = columns_sort_method
|
180
|
+
self.key_chain = key_chain
|
181
|
+
self.last_key = last_key
|
182
|
+
|
183
|
+
self.paginate = paginate
|
184
|
+
self.cancel_is_back = cancel_is_back
|
185
|
+
self.mode_index = mode_index
|
186
|
+
self.modes = modes
|
187
|
+
self.display_modes = display_modes
|
188
|
+
self.require_option = require_option
|
189
|
+
self.option_functions = option_functions
|
190
|
+
self.disabled_keys = disabled_keys
|
191
|
+
|
192
|
+
self.show_footer = show_footer
|
193
|
+
self.footer_string = footer_string
|
194
|
+
self.footer_string_auto_refresh = footer_string_auto_refresh
|
195
|
+
self.footer_string_refresh_function = footer_string_refresh_function
|
196
|
+
self.footer_timer = footer_timer
|
197
|
+
|
198
|
+
|
199
|
+
self.colours_start = colours_start
|
200
|
+
self.colours_end = colours_end
|
201
|
+
self.key_remappings = key_remappings
|
202
|
+
self.keys_dict = keys_dict
|
203
|
+
self.display_infobox = display_infobox
|
204
|
+
self.infobox_items = infobox_items
|
205
|
+
self.infobox_title = infobox_title
|
206
|
+
self.display_only = display_only
|
207
|
+
|
208
|
+
self.editable_columns = editable_columns
|
209
|
+
|
210
|
+
self.centre_in_terminal = centre_in_terminal
|
211
|
+
self.centre_in_terminal_vertical = centre_in_terminal_vertical
|
212
|
+
self.centre_in_cols = centre_in_cols
|
213
|
+
|
214
|
+
self.startup_notification = startup_notification
|
215
|
+
|
216
|
+
|
217
|
+
self.registers = {}
|
218
|
+
self.SORT_METHODS = ['original', 'lexical', 'LEXICAL', 'alphanum', 'ALPHANUM', 'time', 'numerical', 'size']
|
219
|
+
|
220
|
+
curses.set_escdelay(25)
|
221
|
+
|
222
|
+
|
223
|
+
def initialise_variables(self, get_data: bool = False) -> None:
|
224
|
+
""" Initialise the variables that keep track of the data. """
|
225
|
+
|
226
|
+
tracking, ids, cursor_pos_id = False, [], 0
|
227
|
+
|
228
|
+
if get_data and self.refresh_function != None:
|
229
|
+
if self.track_entries_upon_refresh and len(self.items) > 0:
|
230
|
+
tracking = True
|
231
|
+
selected_indices = get_selected_indices(self.selections)
|
232
|
+
ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
|
233
|
+
|
234
|
+
if len(self.indexed_items) > 0 and len(self.indexed_items) >= self.cursor_pos and len(self.indexed_items[0][1]) >= self.id_column:
|
235
|
+
cursor_pos_id = self.indexed_items[self.cursor_pos][1][self.id_column]
|
236
|
+
|
237
|
+
self.items, self.header = self.refresh_function()
|
238
|
+
|
239
|
+
|
240
|
+
if self.items == []: self.items = [[]]
|
241
|
+
## Ensure that items is a List[List[Str]] object
|
242
|
+
if len(self.items) > 0 and not isinstance(self.items[0], list):
|
243
|
+
self.items = [[item] for item in self.items]
|
244
|
+
self.items = [[str(cell) for cell in row] for row in self.items]
|
245
|
+
|
246
|
+
|
247
|
+
# Ensure that header is of the same length as the rows
|
248
|
+
if self.header and len(self.items) > 0 and len(self.header) != len(self.items[0]):
|
249
|
+
self.header = [str(self.header[i]) if i < len(self.header) else "" for i in range(len(self.items[0]))]
|
250
|
+
|
251
|
+
# Constants
|
252
|
+
# DEFAULT_ITEMS_PER_PAGE = os.get_terminal_size().lines - top_gap*2-2-int(bool(header))
|
253
|
+
top_space = self.top_gap
|
254
|
+
if self.title: top_space+=1
|
255
|
+
if self.display_modes: top_space+=1
|
256
|
+
|
257
|
+
h, w = self.stdscr.getmaxyx()
|
258
|
+
|
259
|
+
self.items_per_page = h - top_space-int(bool(self.header)) - 3*int(bool(self.show_footer))
|
260
|
+
if not self.show_footer and self.footer_string: self.items_per_page-=1
|
261
|
+
self.items_per_page = min(h-top_space-2, self.items_per_page)
|
262
|
+
|
263
|
+
|
264
|
+
|
265
|
+
# Initial states
|
266
|
+
if len(self.selections) != len(self.items):
|
267
|
+
self.selections = {i : False if i not in self.selections else bool(self.selections[i]) for i in range(len(self.items))}
|
268
|
+
|
269
|
+
if len(self.require_option) < len(self.items):
|
270
|
+
self.require_option += [False for i in range(len(self.items)-len(self.require_option))]
|
271
|
+
if len(self.option_functions) < len(self.items):
|
272
|
+
self.option_functions += [None for i in range(len(self.items)-len(self.option_functions))]
|
273
|
+
if len(self.items)>0 and len(self.columns_sort_method) < len(self.items[0]):
|
274
|
+
self.columns_sort_method = self.columns_sort_method + [0 for i in range(len(self.items[0])-len(self.columns_sort_method))]
|
275
|
+
if len(self.items)>0 and len(self.sort_reverse) < len(self.items[0]):
|
276
|
+
self.sort_reverse = self.sort_reverse + [False for i in range(len(self.items[0])-len(self.sort_reverse))]
|
277
|
+
if len(self.items)>0 and len(self.editable_columns) < len(self.items[0]):
|
278
|
+
self.editable_columns = self.editable_columns + [False for i in range(len(self.items[0])-len(self.editable_columns))]
|
279
|
+
if len(self.items)>0 and len(self.column_indices) < len(self.items[0]):
|
280
|
+
self.column_indices = self.column_indices + [i for i in range(len(self.column_indices), len(self.items[0]))]
|
281
|
+
|
282
|
+
|
283
|
+
|
284
|
+
# items2 = [[row[self.column_indices[i]] for i in range(len(row))] for row in self.items]
|
285
|
+
# self.indexed_items = list(enumerate(items2))
|
286
|
+
if self.items == [[]]: self.indexed_items = []
|
287
|
+
else: self.indexed_items = list(enumerate(self.items))
|
288
|
+
|
289
|
+
# If a filter is passed then refilter
|
290
|
+
if self.filter_query:
|
291
|
+
# prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
|
292
|
+
# prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
|
293
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
294
|
+
if self.cursor_pos in [x[0] for x in self.indexed_items]: self.cursor_pos = [x[0] for x in self.indexed_items].index(self.cursor_pos)
|
295
|
+
else: self.cursor_pos = 0
|
296
|
+
if self.search_query:
|
297
|
+
return_val, tmp_cursor, tmp_index, tmp_count, tmp_highlights = search(
|
298
|
+
query=self.search_query,
|
299
|
+
indexed_items=self.indexed_items,
|
300
|
+
highlights=self.highlights,
|
301
|
+
cursor_pos=self.cursor_pos,
|
302
|
+
unselectable_indices=self.unselectable_indices,
|
303
|
+
continue_search=True,
|
304
|
+
)
|
305
|
+
if return_val:
|
306
|
+
self.cursor_pos, self.search_index, self.search_count, self.highlights = tmp_cursor, tmp_index, tmp_count, tmp_highlights
|
307
|
+
# If a sort is passed
|
308
|
+
if len(self.indexed_items) > 0:
|
309
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
310
|
+
if len(self.items[0]) == 1:
|
311
|
+
self.number_columns = False
|
312
|
+
|
313
|
+
|
314
|
+
|
315
|
+
h, w = self.stdscr.getmaxyx()
|
316
|
+
|
317
|
+
# Adjust variables to ensure correctness if errors
|
318
|
+
## Move to a selectable row (if applicable)
|
319
|
+
if len(self.items) <= len(self.unselectable_indices): self.unselectable_indices = []
|
320
|
+
new_pos = (self.cursor_pos)%len(self.items)
|
321
|
+
while new_pos in self.unselectable_indices and new_pos != self.cursor_pos:
|
322
|
+
new_pos = (new_pos + 1) % len(self.items)
|
323
|
+
|
324
|
+
assert new_pos < len(self.items)
|
325
|
+
self.cursor_pos = new_pos
|
326
|
+
|
327
|
+
|
328
|
+
if tracking and len(self.items) > 1:
|
329
|
+
selected_indices = []
|
330
|
+
all_ids = [item[self.id_column] for item in self.items]
|
331
|
+
self.selections = {i: False for i in range(len(self.items))}
|
332
|
+
for id in ids:
|
333
|
+
if id in all_ids:
|
334
|
+
selected_indices.append(all_ids.index(id))
|
335
|
+
self.selections[all_ids.index(id)] = True
|
336
|
+
|
337
|
+
if cursor_pos_id in all_ids:
|
338
|
+
cursor_pos_x = all_ids.index(cursor_pos_id)
|
339
|
+
if cursor_pos_x in [i[0] for i in self.indexed_items]:
|
340
|
+
self.cursor_pos = [i[0] for i in self.indexed_items].index(cursor_pos_x)
|
341
|
+
|
342
|
+
|
343
|
+
|
344
|
+
def move_column(self, direction: int) -> None:
|
345
|
+
"""
|
346
|
+
Cycles the column $direction places.
|
347
|
+
E.g., If $direction == -1 and the sort column is 3, then column 3 will swap with column 2
|
348
|
+
in each of the rows in $items and 2 will become the new sort column.
|
349
|
+
|
350
|
+
sort_column = 3, direction = -1
|
351
|
+
[[0,1,2,*3*,4],
|
352
|
+
[5,6,7,*8*,9]]
|
353
|
+
-->
|
354
|
+
[[0,1,*3*,2,4],
|
355
|
+
[5,6,*8*,7,9]]
|
356
|
+
|
357
|
+
returns:
|
358
|
+
adjusted items, header, sort_column and column_widths
|
359
|
+
"""
|
360
|
+
if len(self.items) < 1: return None
|
361
|
+
if (self.sort_column+direction) < 0 or (self.sort_column+direction) >= len(self.items[0]): return None
|
362
|
+
|
363
|
+
new_index = self.sort_column + direction
|
364
|
+
|
365
|
+
# Swap columns in each row
|
366
|
+
for row in self.items:
|
367
|
+
row[self.sort_column], row[new_index] = row[new_index], row[self.sort_column]
|
368
|
+
if self.header:
|
369
|
+
self.header[self.sort_column], self.header[new_index] = self.header[new_index], self.header[self.sort_column]
|
370
|
+
|
371
|
+
# Swap column widths
|
372
|
+
self.column_widths[self.sort_column], self.column_widths[new_index] = self.column_widths[new_index], self.column_widths[self.sort_column]
|
373
|
+
|
374
|
+
# Update current column index
|
375
|
+
self.sort_column = new_index
|
376
|
+
|
377
|
+
def draw_screen(self, indexed_items: list[Tuple[int, list[str]]], highlights: list[dict] = [{}], clear: bool = True) -> None:
|
378
|
+
""" Draw the list_picker screen. """
|
379
|
+
|
380
|
+
if clear:
|
381
|
+
self.stdscr.erase()
|
382
|
+
|
383
|
+
## Terminal too small to display list_picker
|
384
|
+
h, w = self.stdscr.getmaxyx()
|
385
|
+
if h<3 or w<len("Terminal"): return None
|
386
|
+
if (self.show_footer or self.footer_string) and (h<12 or w<35) or (h<12 and w<10):
|
387
|
+
self.stdscr.addstr(h//2-1, (w-len("Terminal"))//2, "Terminal")
|
388
|
+
self.stdscr.addstr(h//2, (w-len("Too"))//2, "Too")
|
389
|
+
self.stdscr.addstr(h//2+1, (w-len("Small"))//2, "Small")
|
390
|
+
return None
|
391
|
+
|
392
|
+
top_space = self.top_gap
|
393
|
+
if self.title: top_space+=1
|
394
|
+
if self.display_modes: top_space+=1
|
395
|
+
|
396
|
+
self.items_per_page = h - top_space-int(bool(self.header)) - 3*int(bool(self.show_footer))
|
397
|
+
if not self.show_footer and self.footer_string: self.items_per_page-=1
|
398
|
+
self.items_per_page = min(h-top_space-2, self.items_per_page)
|
399
|
+
|
400
|
+
|
401
|
+
# Determine which rows are to be displayed on the current screen
|
402
|
+
## Paginate
|
403
|
+
if self.paginate:
|
404
|
+
start_index = (self.cursor_pos//self.items_per_page) * self.items_per_page
|
405
|
+
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
406
|
+
## Scroll
|
407
|
+
else:
|
408
|
+
scrolloff = self.items_per_page//2
|
409
|
+
start_index = max(0, min(self.cursor_pos - (self.items_per_page-scrolloff), len(self.indexed_items)-self.items_per_page))
|
410
|
+
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
411
|
+
if len(self.indexed_items) == 0: start_index, end_index = 0, 0
|
412
|
+
|
413
|
+
# self.column_widths = get_column_widths(self.items, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns)
|
414
|
+
# Determine widths based only on the currently indexed rows
|
415
|
+
# rows = [v[1] for v in self.indexed_items] if len(self.indexed_items) else self.items
|
416
|
+
# Determine widths based only on the currently displayed indexed rows
|
417
|
+
rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
|
418
|
+
self.column_widths = get_column_widths(rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns)
|
419
|
+
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
420
|
+
visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
|
421
|
+
startx = 0 if self.highlight_full_row else 2
|
422
|
+
if visible_columns_total_width < w and self.centre_in_terminal:
|
423
|
+
startx += (w - visible_columns_total_width) // 2
|
424
|
+
|
425
|
+
top_space = self.top_gap
|
426
|
+
|
427
|
+
## Display title (if applicable)
|
428
|
+
if self.title:
|
429
|
+
padded_title = f" {self.title.strip()} "
|
430
|
+
# self.stdscr.addstr(self.top_gap, 0, f"{' ':^{w}}", curses.color_pair(self.colours_start+16) | curses.A_UNDERLINE)
|
431
|
+
self.stdscr.addstr(self.top_gap, 0, f"{' ':^{w}}", curses.color_pair(self.colours_start+16))
|
432
|
+
title_x = (w-wcswidth(padded_title))//2
|
433
|
+
# title = f"{title:^{w}}"
|
434
|
+
self.stdscr.addstr(self.top_gap, title_x, padded_title, curses.color_pair(self.colours_start+16) | curses.A_BOLD)
|
435
|
+
top_space += 1
|
436
|
+
|
437
|
+
## Display modes
|
438
|
+
if self.display_modes and self.modes not in [[{}], []]:
|
439
|
+
self.stdscr.addstr(top_space, 0, ' '*w, curses.A_REVERSE)
|
440
|
+
modes_list = [f"{mode['name']}" if 'name' in mode else f"{i}. " for i, mode in enumerate(self.modes)]
|
441
|
+
# mode_colours = [mode["colour"] for mode ]
|
442
|
+
mode_widths = get_mode_widths(modes_list)
|
443
|
+
split_space = (w-sum(mode_widths))//len(self.modes)
|
444
|
+
xmode = 0
|
445
|
+
for i, mode in enumerate(modes_list):
|
446
|
+
if i == len(modes_list)-1:
|
447
|
+
mode_str = f"{mode:^{mode_widths[i]+split_space+(w-sum(mode_widths))%len(self.modes)}}"
|
448
|
+
else:
|
449
|
+
mode_str = f"{mode:^{mode_widths[i]+split_space}}"
|
450
|
+
# current mode
|
451
|
+
if i == self.mode_index:
|
452
|
+
self.stdscr.addstr(top_space, xmode, mode_str, curses.color_pair(self.colours_start+14) | curses.A_BOLD)
|
453
|
+
# other modes
|
454
|
+
else:
|
455
|
+
self.stdscr.addstr(top_space, xmode, mode_str, curses.color_pair(self.colours_start+15) | curses.A_UNDERLINE)
|
456
|
+
xmode += split_space+mode_widths[i]
|
457
|
+
top_space += 1
|
458
|
+
|
459
|
+
## Display header
|
460
|
+
if self.header:
|
461
|
+
header_str = ""
|
462
|
+
up_to_selected_col = ""
|
463
|
+
for i in range(len(self.header)):
|
464
|
+
if i == self.sort_column: up_to_selected_col = header_str
|
465
|
+
if i in self.hidden_columns: continue
|
466
|
+
number = f"{i}. " if self.number_columns else ""
|
467
|
+
number = f"{intStringToExponentString(str(i))}. " if self.number_columns else ""
|
468
|
+
header_str += number
|
469
|
+
header_str +=f"{self.header[i]:^{self.column_widths[i]}}"
|
470
|
+
header_str += " "
|
471
|
+
|
472
|
+
self.stdscr.addstr(top_space, 0, ' '*w, curses.color_pair(self.colours_start+4) | curses.A_BOLD)
|
473
|
+
self.stdscr.addstr(top_space, startx, header_str[:min(w-startx, visible_columns_total_width+1)], curses.color_pair(self.colours_start+4) | curses.A_BOLD)
|
474
|
+
|
475
|
+
# Highlight sort column
|
476
|
+
if self.sort_column != None and self.sort_column not in self.hidden_columns and len(up_to_selected_col) + 1 < w and len(self.header) > 1:
|
477
|
+
number = f"{self.sort_column}. " if self.number_columns else ""
|
478
|
+
number = f"{intStringToExponentString(self.sort_column)}. " if self.number_columns else ""
|
479
|
+
self.stdscr.addstr(top_space, startx + len(up_to_selected_col), (number+f"{self.header[self.sort_column]:^{self.column_widths[self.sort_column]}}")[:w-len(up_to_selected_col)-startx], curses.color_pair(self.colours_start+19) | curses.A_BOLD)
|
480
|
+
|
481
|
+
if self.centre_in_terminal_vertical and len(self.indexed_items) < self.items_per_page:
|
482
|
+
top_space += (self.items_per_page - len(self.indexed_items)) //2
|
483
|
+
|
484
|
+
## Display rows and highlights
|
485
|
+
for idx in range(start_index, end_index):
|
486
|
+
item = self.indexed_items[idx]
|
487
|
+
y = idx - start_index + top_space + int(bool(self.header))
|
488
|
+
|
489
|
+
row_str = format_row(item[1], self.hidden_columns, self.column_widths, self.separator, self.centre_in_cols)
|
490
|
+
if idx == self.cursor_pos:
|
491
|
+
self.stdscr.addstr(y, startx, row_str[:min(w-startx, visible_columns_total_width)], curses.color_pair(self.colours_start+5) | curses.A_BOLD)
|
492
|
+
else:
|
493
|
+
self.stdscr.addstr(y, startx, row_str[:min(w-startx, visible_columns_total_width)], curses.color_pair(self.colours_start+2))
|
494
|
+
# Highlight the whole string of the selected rows
|
495
|
+
if self.highlight_full_row:
|
496
|
+
if self.selections[item[0]]:
|
497
|
+
self.stdscr.addstr(y, startx, row_str[:min(w-startx, visible_columns_total_width)], curses.color_pair(self.colours_start+1))
|
498
|
+
# Visually selected
|
499
|
+
if self.is_selecting and self.start_selection <= idx <= self.cursor_pos:
|
500
|
+
self.stdscr.addstr(y, startx, row_str[:min(w-startx, visible_columns_total_width)], curses.color_pair(self.colours_start+1))
|
501
|
+
elif self.is_selecting and self.start_selection >= idx >= self.cursor_pos:
|
502
|
+
self.stdscr.addstr(y, startx, row_str[:min(w-startx, visible_columns_total_width)], curses.color_pair(self.colours_start+1))
|
503
|
+
# Visually deslected
|
504
|
+
if self.is_deselecting and self.start_selection >= idx >= self.cursor_pos:
|
505
|
+
self.stdscr.addstr(y, startx, row_str[:min(w-startx, visible_columns_total_width)], curses.color_pair(self.colours_start+1))
|
506
|
+
elif self.is_deselecting and self.start_selection <= idx <= self.cursor_pos:
|
507
|
+
self.stdscr.addstr(y, startx, row_str[:min(w-startx, visible_columns_total_width)], curses.color_pair(self.colours_start+1))
|
508
|
+
|
509
|
+
# Highlight the first char of the selected rows
|
510
|
+
else:
|
511
|
+
if self.selections[item[0]]:
|
512
|
+
self.stdscr.addstr(y, max(startx-2,0), ' ', curses.color_pair(self.colours_start+1))
|
513
|
+
# Visually selected
|
514
|
+
if self.is_selecting and self.start_selection <= idx <= self.cursor_pos:
|
515
|
+
self.stdscr.addstr(y, max(startx-2,0), ' ', curses.color_pair(self.colours_start+1))
|
516
|
+
elif self.is_selecting and self.start_selection >= idx >= self.cursor_pos:
|
517
|
+
self.stdscr.addstr(y, max(startx-2,0), ' ', curses.color_pair(self.colours_start+1))
|
518
|
+
# Visually deslected
|
519
|
+
if self.is_deselecting and self.start_selection >= idx >= self.cursor_pos:
|
520
|
+
self.stdscr.addstr(y, max(startx-2,0), ' ', curses.color_pair(self.colours_start+10))
|
521
|
+
elif self.is_deselecting and self.start_selection <= idx <= self.cursor_pos:
|
522
|
+
self.stdscr.addstr(y, max(startx-2,0), ' ', curses.color_pair(self.colours_start+10))
|
523
|
+
|
524
|
+
# if not highlights_hide:
|
525
|
+
if not self.highlights_hide and idx != self.cursor_pos:
|
526
|
+
for highlight in self.highlights:
|
527
|
+
try:
|
528
|
+
if highlight["field"] == "all":
|
529
|
+
match = re.search(highlight["match"], row_str, re.IGNORECASE)
|
530
|
+
if not match: continue
|
531
|
+
highlight_start = match.start()
|
532
|
+
highlight_end = match.end()
|
533
|
+
elif type(highlight["field"]) == type(4) and highlight["field"] not in self.hidden_columns:
|
534
|
+
match = re.search(highlight["match"], truncate_to_display_width(item[1][highlight["field"]], self.column_widths[highlight["field"]], self.centre_in_cols), re.IGNORECASE)
|
535
|
+
if not match: continue
|
536
|
+
field_start = sum([width for i, width in enumerate(self.column_widths[:highlight["field"]]) if i not in self.hidden_columns]) + sum([1 for i in range(highlight["field"]) if i not in self.hidden_columns])*wcswidth(self.separator)
|
537
|
+
highlight_start = field_start + match.start()
|
538
|
+
highlight_end = match.end() + field_start
|
539
|
+
else:
|
540
|
+
continue
|
541
|
+
color_pair = curses.color_pair(self.colours_start+highlight["color"]) # Selected item
|
542
|
+
if idx == self.cursor_pos:
|
543
|
+
color_pair = curses.color_pair(self.colours_start+highlight["color"]) | curses.A_REVERSE
|
544
|
+
self.stdscr.addstr(y, startx+highlight_start, row_str[highlight_start:min(w-startx, highlight_end)], curses.color_pair(self.colours_start+highlight["color"]) | curses.A_BOLD)
|
545
|
+
except:
|
546
|
+
pass
|
547
|
+
|
548
|
+
## Display scrollbar
|
549
|
+
if self.scroll_bar and len(self.indexed_items) and len(self.indexed_items) > (self.items_per_page):
|
550
|
+
scroll_bar_length = int(self.items_per_page*self.items_per_page/len(self.indexed_items))
|
551
|
+
if self.cursor_pos <= self.items_per_page//2:
|
552
|
+
scroll_bar_start=top_space+int(bool(self.header))
|
553
|
+
elif self.cursor_pos + self.items_per_page//2 >= len(self.indexed_items):
|
554
|
+
scroll_bar_start = h - int(bool(self.show_footer))*3 - scroll_bar_length
|
555
|
+
else:
|
556
|
+
scroll_bar_start = int(((self.cursor_pos)/len(self.indexed_items))*self.items_per_page)+top_space+int(bool(self.header)) - scroll_bar_length//2
|
557
|
+
scroll_bar_length = min(scroll_bar_length, h - scroll_bar_start-1)
|
558
|
+
for i in range(scroll_bar_length):
|
559
|
+
v = max(top_space+int(bool(self.header)), scroll_bar_start-scroll_bar_length//2)
|
560
|
+
self.stdscr.addstr(scroll_bar_start+i, w-1, ' ', curses.color_pair(self.colours_start+18))
|
561
|
+
|
562
|
+
# Display refresh symbol
|
563
|
+
if self.auto_refresh:
|
564
|
+
self.stdscr.addstr(0,w-3," ", curses.color_pair(self.colours_start+23) | curses.A_BOLD)
|
565
|
+
|
566
|
+
## Display footer
|
567
|
+
if self.show_footer:
|
568
|
+
# Fill background
|
569
|
+
self.stdscr.addstr(h-3, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
570
|
+
self.stdscr.addstr(h-2, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
571
|
+
self.stdscr.addstr(h-1, 0, ' '*(w-1), curses.color_pair(self.colours_start+20)) # Problem with curses that you can't write to the last char
|
572
|
+
|
573
|
+
if self.filter_query:
|
574
|
+
self.stdscr.addstr(h - 2, 2, f" Filter: {self.filter_query} "[:w-40], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
575
|
+
if self.search_query:
|
576
|
+
self.stdscr.addstr(h - 3, 2, f" Search: {self.search_query} [{self.search_index}/{self.search_count}] "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
577
|
+
if self.user_opts:
|
578
|
+
self.stdscr.addstr(h-1, 2, f" Opts: {self.user_opts} "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
579
|
+
# Display sort information
|
580
|
+
sort_column_info = f"{self.sort_column if self.sort_column is not None else 'None'}"
|
581
|
+
sort_method_info = f"{self.SORT_METHODS[self.columns_sort_method[self.sort_column]]}" if self.sort_column != None else "NA"
|
582
|
+
|
583
|
+
|
584
|
+
## RIGHT
|
585
|
+
# Sort status
|
586
|
+
sort_order_info = "Desc." if self.sort_reverse[self.sort_column] else "Asc."
|
587
|
+
sort_disp_str = f" Sort: ({sort_column_info}, {sort_method_info}, {sort_order_info}) "
|
588
|
+
self.stdscr.addstr(h - 2, w-35, f"{sort_disp_str:>34}", curses.color_pair(self.colours_start+20))
|
589
|
+
|
590
|
+
|
591
|
+
if self.footer_string:
|
592
|
+
# footer_string_width = min(w, max(len(self.footer_string), w//3, 39))
|
593
|
+
footer_string_width = min(w-1, max(len(self.footer_string), 50))
|
594
|
+
disp_string = f"{self.footer_string[:footer_string_width]:>{footer_string_width-1}} "
|
595
|
+
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
596
|
+
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
597
|
+
else:
|
598
|
+
# Display cursor mode
|
599
|
+
select_mode = "Cursor"
|
600
|
+
if self.is_selecting: select_mode = "Visual Selection"
|
601
|
+
elif self.is_deselecting: select_mode = "Visual deselection"
|
602
|
+
self.stdscr.addstr(h - 1, w-35, f"{select_mode:>33} ", curses.color_pair(self.colours_start+20))
|
603
|
+
# Display selection count
|
604
|
+
selected_count = sum(self.selections.values())
|
605
|
+
if self.paginate:
|
606
|
+
cursor_disp_str = f" {self.cursor_pos+1}/{len(self.indexed_items)} Page {self.cursor_pos//self.items_per_page + 1}/{(len(self.indexed_items) + self.items_per_page - 1) // self.items_per_page} Selected {selected_count}"
|
607
|
+
self.stdscr.addstr(h - 3, w-35, f"{cursor_disp_str:>33} ", curses.color_pair(self.colours_start+20))
|
608
|
+
else:
|
609
|
+
cursor_disp_str = f" {self.cursor_pos+1}/{len(self.indexed_items)} | Selected {selected_count}"
|
610
|
+
self.stdscr.addstr(h - 3, w-35, f"{cursor_disp_str:>33} ", curses.color_pair(self.colours_start+20))
|
611
|
+
|
612
|
+
self.stdscr.refresh()
|
613
|
+
elif self.footer_string:
|
614
|
+
footer_string_width = min(w-1, len(self.footer_string)+2)
|
615
|
+
disp_string = f" {self.footer_string[:footer_string_width]:>{footer_string_width-2}} "
|
616
|
+
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
617
|
+
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
618
|
+
|
619
|
+
## Display infobox
|
620
|
+
if self.display_infobox:
|
621
|
+
self.infobox(self.stdscr, message=self.infobox_items, title=self.infobox_title)
|
622
|
+
# self.stdscr.timeout(2000) # timeout is set to 50 in order to get the infobox to be displayed so here we reset it to 2000
|
623
|
+
|
624
|
+
|
625
|
+
|
626
|
+
def infobox(self, stdscr: curses.window, message: str ="", title: str ="Infobox", colours_end: int = 0, duration: int = 4) -> curses.window:
|
627
|
+
""" Display non-interactive infobox window. """
|
628
|
+
h, w = stdscr.getmaxyx()
|
629
|
+
notification_width, notification_height = w//2, 3*h//5
|
630
|
+
message_width = notification_width-5
|
631
|
+
|
632
|
+
if not message: message = "!!"
|
633
|
+
if isinstance(message, str):
|
634
|
+
submenu_items = [" "+message[i*message_width:(i+1)*message_width] for i in range(len(message)//message_width+1)]
|
635
|
+
else:
|
636
|
+
submenu_items = message
|
637
|
+
|
638
|
+
notification_remap_keys = {
|
639
|
+
curses.KEY_RESIZE: curses.KEY_F5,
|
640
|
+
27: ord('q')
|
641
|
+
}
|
642
|
+
if len(submenu_items) > notification_height - 2:
|
643
|
+
submenu_items = submenu_items[:notification_height-3] + [f"{'....':^{notification_width}}"]
|
644
|
+
while True:
|
645
|
+
h, w = stdscr.getmaxyx()
|
646
|
+
|
647
|
+
submenu_win = curses.newwin(notification_height, notification_width, 3, w - (notification_width+4))
|
648
|
+
infobox_data = {
|
649
|
+
"items": submenu_items,
|
650
|
+
"colours": notification_colours,
|
651
|
+
"colours_start": 50,
|
652
|
+
"disabled_keys": [ord('z'), ord('c')],
|
653
|
+
"show_footer": False,
|
654
|
+
"top_gap": 0,
|
655
|
+
"key_remappings": notification_remap_keys,
|
656
|
+
"display_only": True,
|
657
|
+
"hidden_columns": [],
|
658
|
+
"title": title,
|
659
|
+
}
|
660
|
+
|
661
|
+
OptionPicker = Picker(submenu_win, **infobox_data)
|
662
|
+
s, o, f = OptionPicker.run()
|
663
|
+
if o != "refresh": break
|
664
|
+
|
665
|
+
return submenu_win
|
666
|
+
|
667
|
+
|
668
|
+
|
669
|
+
def get_function_data(self) -> dict:
|
670
|
+
""" Returns a dict of the main variables needed to restore the state of list_pikcer. """
|
671
|
+
function_data = {
|
672
|
+
"selections": self.selections,
|
673
|
+
"items_per_page": self.items_per_page,
|
674
|
+
"current_row": self.current_row,
|
675
|
+
"current_page": self.current_page,
|
676
|
+
"cursor_pos": self.cursor_pos,
|
677
|
+
"colours": self.colours,
|
678
|
+
"colour_theme_number": self.colour_theme_number,
|
679
|
+
"sort_column": self.sort_column,
|
680
|
+
"sort_method": self.sort_method,
|
681
|
+
"sort_reverse": self.sort_reverse,
|
682
|
+
"hidden_columns": self.hidden_columns,
|
683
|
+
"is_selecting": self.is_selecting,
|
684
|
+
"is_deselecting": self.is_deselecting,
|
685
|
+
"user_opts": self.user_opts,
|
686
|
+
"user_settings": self.user_settings,
|
687
|
+
"separator": self.separator,
|
688
|
+
"search_query": self.search_query,
|
689
|
+
"search_count": self.search_count,
|
690
|
+
"search_index": self.search_index,
|
691
|
+
"filter_query": self.filter_query,
|
692
|
+
"indexed_items": self.indexed_items,
|
693
|
+
"start_selection": self.start_selection,
|
694
|
+
"end_selection": self.end_selection,
|
695
|
+
"highlights": self.highlights,
|
696
|
+
"max_column_width": self.max_column_width,
|
697
|
+
"column_indices": self.column_indices,
|
698
|
+
"mode_index": self.mode_index,
|
699
|
+
"modes": self.modes,
|
700
|
+
"title": self.title,
|
701
|
+
"display_modes": self.display_modes,
|
702
|
+
"require_option": self.require_option,
|
703
|
+
"option_functions": self.option_functions,
|
704
|
+
"top_gap": self.top_gap,
|
705
|
+
"number_columns": self.number_columns,
|
706
|
+
"items": self.items,
|
707
|
+
"indexed_items": self.indexed_items,
|
708
|
+
"header": self.header,
|
709
|
+
"scroll_bar": self.scroll_bar,
|
710
|
+
"columns_sort_method": self.columns_sort_method,
|
711
|
+
"disabled_keys": self.disabled_keys,
|
712
|
+
"show_footer": self.show_footer,
|
713
|
+
"footer_string": self.footer_string,
|
714
|
+
"footer_string_auto_refresh": self.footer_string_auto_refresh,
|
715
|
+
"footer_string_refresh_function": self.footer_string_refresh_function,
|
716
|
+
"footer_timer": self.footer_timer,
|
717
|
+
"colours_start": self.colours_start,
|
718
|
+
"colours_end": self.colours_end,
|
719
|
+
"display_only": self.display_only,
|
720
|
+
"infobox_items": self.infobox_items,
|
721
|
+
"display_infobox": self.display_infobox,
|
722
|
+
"infobox_title": self.infobox_title,
|
723
|
+
"key_remappings": self.key_remappings,
|
724
|
+
"auto_refresh": self.auto_refresh,
|
725
|
+
"get_new_data": self.get_new_data,
|
726
|
+
"refresh_function": self.refresh_function,
|
727
|
+
"get_data_startup": self.get_data_startup,
|
728
|
+
"editable_columns": self.editable_columns,
|
729
|
+
"last_key": self.last_key,
|
730
|
+
"centre_in_terminal": self.centre_in_terminal,
|
731
|
+
"centre_in_terminal_vertical": self.centre_in_terminal_vertical,
|
732
|
+
"centre_in_cols": self.centre_in_cols,
|
733
|
+
"highlight_full_row": self.highlight_full_row,
|
734
|
+
"column_widths": self.column_widths,
|
735
|
+
"track_entries_upon_refresh": self.track_entries_upon_refresh,
|
736
|
+
"id_column": self.id_column,
|
737
|
+
"startup_notification": self.startup_notification,
|
738
|
+
"keys_dict": self.keys_dict,
|
739
|
+
"cancel_is_back": self.cancel_is_back,
|
740
|
+
"paginate": self.paginate,
|
741
|
+
|
742
|
+
}
|
743
|
+
return function_data
|
744
|
+
|
745
|
+
def set_function_data(self, function_data: dict) -> None:
|
746
|
+
""" Set variables from state dict containing core variables."""
|
747
|
+
|
748
|
+
if "items" in function_data: self.items = function_data["items"]
|
749
|
+
if "header" in function_data: self.header = function_data["header"]
|
750
|
+
self.indexed_items = function_data["indexed_items"] if "indexed_items" in function_data else []
|
751
|
+
|
752
|
+
|
753
|
+
|
754
|
+
def delete_entries(self) -> None:
|
755
|
+
""" Delete entries from view. """
|
756
|
+
# Remove selected items from the list
|
757
|
+
selected_indices = [index for index, selected in self.selections.items() if selected]
|
758
|
+
if not selected_indices:
|
759
|
+
# Remove the currently focused item if nothing is selected
|
760
|
+
selected_indices = [self.indexed_items[self.cursor_pos][0]]
|
761
|
+
|
762
|
+
self.items = [item for i, item in enumerate(self.items) if i not in selected_indices]
|
763
|
+
self.indexed_items = [(i, item) for i, item in enumerate(self.items)]
|
764
|
+
self.selections = {i:False for i in range(len(self.indexed_items))}
|
765
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
766
|
+
|
767
|
+
|
768
|
+
def choose_option(
|
769
|
+
self,
|
770
|
+
stdscr: curses.window,
|
771
|
+
options: list[list[str]] =[],
|
772
|
+
field_name: str = "Input",
|
773
|
+
x:int=0,
|
774
|
+
y:int=0,
|
775
|
+
literal:bool=False,
|
776
|
+
colours_start:int=0,
|
777
|
+
header: list[str] = [],
|
778
|
+
require_option:list = [],
|
779
|
+
option_functions: list = [],
|
780
|
+
) -> Tuple[dict, str, dict]:
|
781
|
+
"""
|
782
|
+
Display input field at x,y
|
783
|
+
|
784
|
+
---Arguments
|
785
|
+
stdscr: curses screen
|
786
|
+
usrtxt (str): text to be edited by the user
|
787
|
+
field_name (str): The text to be displayed at the start of the text input
|
788
|
+
x (int): prompt begins at (x,y) in the screen given
|
789
|
+
y (int): prompt begins at (x,y) in the screen given
|
790
|
+
colours_start (bool): start index of curses init_pair.
|
791
|
+
|
792
|
+
---Returns
|
793
|
+
usrtxt, return_code
|
794
|
+
usrtxt: the text inputted by the user
|
795
|
+
return_code:
|
796
|
+
0: user hit escape
|
797
|
+
1: user hit return
|
798
|
+
"""
|
799
|
+
if options == []: options = [[f"{i}"] for i in range(10)]
|
800
|
+
cursor = 0
|
801
|
+
|
802
|
+
|
803
|
+
option_picker_data = {
|
804
|
+
"items": options,
|
805
|
+
"colours": notification_colours,
|
806
|
+
"colours_start": 50,
|
807
|
+
"title":field_name,
|
808
|
+
"header":header,
|
809
|
+
"hidden_columns":[],
|
810
|
+
"require_option":require_option,
|
811
|
+
"keys_dict": options_keys,
|
812
|
+
"show_footer": False,
|
813
|
+
"cancel_is_back": True,
|
814
|
+
}
|
815
|
+
while True:
|
816
|
+
h, w = stdscr.getmaxyx()
|
817
|
+
|
818
|
+
choose_opts_widths = get_column_widths(options)
|
819
|
+
window_width = min(max(sum(choose_opts_widths) + 6, 50) + 6, w)
|
820
|
+
window_height = min(h//2, max(6, len(options)+2))
|
821
|
+
|
822
|
+
submenu_win = curses.newwin(window_height, window_width, (h-window_height)//2, (w-window_width)//2)
|
823
|
+
submenu_win.keypad(True)
|
824
|
+
OptionPicker = Picker(submenu_win, **option_picker_data)
|
825
|
+
s, o, f = OptionPicker.run()
|
826
|
+
|
827
|
+
if o == "refresh":
|
828
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
829
|
+
continue
|
830
|
+
if s:
|
831
|
+
return {x: options[x] for x in s}, o, f
|
832
|
+
return {}, "", f
|
833
|
+
|
834
|
+
|
835
|
+
|
836
|
+
def notification(self, stdscr: curses.window, message: str="", title:str="Notification", colours_end: int=0, duration:int=4) -> None:
|
837
|
+
""" Notification box. """
|
838
|
+
notification_width, notification_height = 50, 7
|
839
|
+
message_width = notification_width-5
|
840
|
+
|
841
|
+
if not message: message = "!!"
|
842
|
+
submenu_items = [" "+message[i*message_width:(i+1)*message_width] for i in range(len(message)//message_width+1)]
|
843
|
+
|
844
|
+
notification_remap_keys = {
|
845
|
+
curses.KEY_RESIZE: curses.KEY_F5,
|
846
|
+
27: ord('q')
|
847
|
+
}
|
848
|
+
while True:
|
849
|
+
h, w = stdscr.getmaxyx()
|
850
|
+
|
851
|
+
submenu_win = curses.newwin(notification_height, notification_width, 3, w - (notification_width+4))
|
852
|
+
notification_data = {
|
853
|
+
"items": submenu_items,
|
854
|
+
"title": title,
|
855
|
+
"colours_start": 50,
|
856
|
+
"show_footer": False,
|
857
|
+
"centre_in_terminal": True,
|
858
|
+
"centre_in_terminal_vertical": True,
|
859
|
+
"centre_in_cols": True,
|
860
|
+
"hidden_columns": [],
|
861
|
+
"keys_dict": notification_keys,
|
862
|
+
"disabled_keys": [ord('z'), ord('c')],
|
863
|
+
"highlight_full_row": True,
|
864
|
+
"top_gap": 0,
|
865
|
+
"cancel_is_back": True,
|
866
|
+
|
867
|
+
}
|
868
|
+
OptionPicker = Picker(submenu_win, **notification_data)
|
869
|
+
s, o, f = OptionPicker.run()
|
870
|
+
|
871
|
+
if o != "refresh": break
|
872
|
+
submenu_win.clear()
|
873
|
+
submenu_win.refresh()
|
874
|
+
del submenu_win
|
875
|
+
stdscr.clear()
|
876
|
+
stdscr.refresh()
|
877
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
878
|
+
# set_colours(colours=get_colours(0))
|
879
|
+
|
880
|
+
def toggle_column_visibility(self, col_index:int) -> None:
|
881
|
+
""" Toggle the visibility of the column at col_index. """
|
882
|
+
if 0 <= col_index < len(self.items[0]):
|
883
|
+
if col_index in self.hidden_columns:
|
884
|
+
self.hidden_columns.remove(col_index)
|
885
|
+
else:
|
886
|
+
self.hidden_columns.append(col_index)
|
887
|
+
|
888
|
+
def apply_settings(self) -> None:
|
889
|
+
""" The users settings will be stored in the user_settings variable. This function applies those settings. """
|
890
|
+
|
891
|
+
# settings= usrtxt.split(' ')
|
892
|
+
# split settings and appy them
|
893
|
+
"""
|
894
|
+
![0-9]+ show/hide column
|
895
|
+
s[0-9]+ set column focus for sort
|
896
|
+
g[0-9]+ go to index
|
897
|
+
p[0-9]+ go to page
|
898
|
+
nohl hide search highlights
|
899
|
+
"""
|
900
|
+
if self.user_settings:
|
901
|
+
settings = re.split(r'\s+', self.user_settings)
|
902
|
+
for setting in settings:
|
903
|
+
if len(setting) == 0: continue
|
904
|
+
|
905
|
+
if setting[0] == "!" and len(setting) > 1:
|
906
|
+
if setting[1:].isnumeric():
|
907
|
+
cols = setting[1:].split(",")
|
908
|
+
for col in cols:
|
909
|
+
self.toggle_column_visibility(int(col))
|
910
|
+
elif setting[1] == "r":
|
911
|
+
self.auto_refresh = not self.auto_refresh
|
912
|
+
elif setting[1] == "h":
|
913
|
+
self.highlights_hide = not self.highlights_hide
|
914
|
+
|
915
|
+
elif setting in ["nhl", "nohl", "nohighlights"]:
|
916
|
+
# highlights = [highlight for highlight in highlights if "type" not in highlight or highlight["type"] != "search" ]
|
917
|
+
|
918
|
+
self.highlights_hide = not self.highlights_hide
|
919
|
+
elif setting[0] == "s":
|
920
|
+
if 0 <= int(setting[1:]) < len(self.items[0]):
|
921
|
+
self.sort_column = int(setting[1:])
|
922
|
+
if len(self.indexed_items):
|
923
|
+
current_pos = self.indexed_items[self.cursor_pos][0]
|
924
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort items based on new column
|
925
|
+
if len(self.indexed_items):
|
926
|
+
new_pos = [row[0] for row in self.indexed_items].index(self.current_pos)
|
927
|
+
self.cursor_pos = new_pos
|
928
|
+
elif setting == "ct":
|
929
|
+
self.centre_in_terminal = not self.centre_in_terminal
|
930
|
+
elif setting == "cc":
|
931
|
+
self.centre_in_cols = not self.centre_in_cols
|
932
|
+
elif setting == "cv":
|
933
|
+
self.centre_in_terminal_vertical = not self.centre_in_terminal_vertical
|
934
|
+
elif setting[0] == "":
|
935
|
+
cols = setting[1:].split(",")
|
936
|
+
elif setting == "footer":
|
937
|
+
self.show_footer = not self.show_footer
|
938
|
+
self.initialise_variables()
|
939
|
+
|
940
|
+
elif setting.startswith("cwd="):
|
941
|
+
os.chdir(os.path.expandvars(os.path.expanduser(setting[len("cwd="):])))
|
942
|
+
elif setting == "th":
|
943
|
+
global COLOURS_SET
|
944
|
+
COLOURS_SET = False
|
945
|
+
self.colour_theme_number = (self.colour_theme_number + 1)%get_theme_count()
|
946
|
+
# self.colour_theme_number = int(not bool(self.colour_theme_number))
|
947
|
+
set_colours(self.colour_theme_number)
|
948
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
949
|
+
self.notification(self.stdscr, message=f"Theme {self.colour_theme_number} applied.")
|
950
|
+
|
951
|
+
|
952
|
+
self.user_settings = ""
|
953
|
+
|
954
|
+
def toggle_item(self, index: int) -> None:
|
955
|
+
""" Toggle selection of item at index. """
|
956
|
+
self.selections[index] = not self.selections[index]
|
957
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
958
|
+
|
959
|
+
def select_all(self) -> None:
|
960
|
+
""" Select all in indexed_items. """
|
961
|
+
for i in range(len(self.indexed_items)):
|
962
|
+
self.selections[self.indexed_items[i][0]] = True
|
963
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
964
|
+
|
965
|
+
def deselect_all(self) -> None:
|
966
|
+
""" Deselect all items in indexed_items. """
|
967
|
+
for i in range(len(self.selections)):
|
968
|
+
self.selections[i] = False
|
969
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
970
|
+
|
971
|
+
def handle_visual_selection(self, selecting:bool = True) -> None:
|
972
|
+
""" Toggle visual selection or deselection. """
|
973
|
+
if not self.is_selecting and not self.is_deselecting and len(self.indexed_items) and len(self.indexed_items[0][1]):
|
974
|
+
self.start_selection = self.cursor_pos
|
975
|
+
if selecting:
|
976
|
+
self.is_selecting = True
|
977
|
+
else:
|
978
|
+
self.is_deselecting = True
|
979
|
+
elif self.is_selecting:
|
980
|
+
# end_selection = indexed_items[current_page * items_per_page + current_row][0]
|
981
|
+
self.end_selection = self.cursor_pos
|
982
|
+
if self.start_selection != -1:
|
983
|
+
start = max(min(self.start_selection, self.end_selection), 0)
|
984
|
+
end = min(max(self.start_selection, self.end_selection), len(self.indexed_items)-1)
|
985
|
+
for i in range(start, end + 1):
|
986
|
+
if self.indexed_items[i][0] not in self.unselectable_indices:
|
987
|
+
self.selections[self.indexed_items[i][0]] = True
|
988
|
+
self.start_selection = -1
|
989
|
+
self.end_selection = -1
|
990
|
+
self.is_selecting = False
|
991
|
+
|
992
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
993
|
+
|
994
|
+
elif self.is_deselecting:
|
995
|
+
self.end_selection = self.indexed_items[self.cursor_pos][0]
|
996
|
+
self.end_selection = self.cursor_pos
|
997
|
+
if self.start_selection != -1:
|
998
|
+
start = max(min(self.start_selection, self.end_selection), 0)
|
999
|
+
end = min(max(self.start_selection, self.end_selection), len(self.indexed_items)-1)
|
1000
|
+
for i in range(start, end + 1):
|
1001
|
+
# selections[i] = False
|
1002
|
+
self.selections[self.indexed_items[i][0]] = False
|
1003
|
+
self.start_selection = -1
|
1004
|
+
self.end_selection = -1
|
1005
|
+
self.is_deselecting = False
|
1006
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1007
|
+
|
1008
|
+
def cursor_down(self) -> bool:
|
1009
|
+
""" Move cursor down. """
|
1010
|
+
# Returns: whether page is turned
|
1011
|
+
new_pos = self.cursor_pos + 1
|
1012
|
+
while True:
|
1013
|
+
if new_pos >= len(self.indexed_items): return False
|
1014
|
+
if self.indexed_items[new_pos][0] in self.unselectable_indices: new_pos+=1
|
1015
|
+
else: break
|
1016
|
+
self.cursor_pos = new_pos
|
1017
|
+
return True
|
1018
|
+
|
1019
|
+
def cursor_up(self) -> bool:
|
1020
|
+
""" Move cursor up. """
|
1021
|
+
# Returns: whether page is turned
|
1022
|
+
new_pos = self.cursor_pos - 1
|
1023
|
+
while True:
|
1024
|
+
if new_pos < 0: return False
|
1025
|
+
elif new_pos in self.unselectable_indices: new_pos -= 1
|
1026
|
+
else: break
|
1027
|
+
self.cursor_pos = new_pos
|
1028
|
+
return True
|
1029
|
+
|
1030
|
+
def remapped_key(self, key: int, val: int, key_remappings: dict) -> bool:
|
1031
|
+
""" Check if key has been remapped to val in key_remappings. """
|
1032
|
+
if key in key_remappings:
|
1033
|
+
if key_remappings[key] == val or (isinstance(key_remappings[key], list) and val in key_remappings[key]):
|
1034
|
+
return True
|
1035
|
+
return False
|
1036
|
+
|
1037
|
+
def check_key(self, function: str, key: int, keys_dict: dict) -> bool:
|
1038
|
+
"""
|
1039
|
+
Check if $key is assigned to $function in the keys_dict.
|
1040
|
+
Allows us to redefine functions to different keys in the keys_dict.
|
1041
|
+
|
1042
|
+
E.g., keys_dict = { $key, "help": ord('?') },
|
1043
|
+
"""
|
1044
|
+
if function in keys_dict and key in keys_dict[function]:
|
1045
|
+
return True
|
1046
|
+
return False
|
1047
|
+
|
1048
|
+
def copy_dialog(self) -> None:
|
1049
|
+
copy_header = [
|
1050
|
+
"Representation",
|
1051
|
+
"Columns",
|
1052
|
+
]
|
1053
|
+
options = [
|
1054
|
+
["Python list of lists", "Exclude hidden"],
|
1055
|
+
["Python list of lists", "Include hidden"],
|
1056
|
+
["Tab-separated values", "Exclude hidden"],
|
1057
|
+
["Tab-separated values", "Include hidden"],
|
1058
|
+
["Comma-separated values", "Exclude hidden"],
|
1059
|
+
["Comma-separated values", "Include hidden"],
|
1060
|
+
["Custom separator", "Exclude hidden"],
|
1061
|
+
["Custom separator", "Include hidden"],
|
1062
|
+
]
|
1063
|
+
require_option = [False, False, False, False, False, False, True, True]
|
1064
|
+
s, o, f = self.choose_option(self.stdscr, options=options, field_name="Copy selected", header=copy_header, require_option=require_option)
|
1065
|
+
|
1066
|
+
|
1067
|
+
funcs = [
|
1068
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="python", copy_hidden_cols=False),
|
1069
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="python", copy_hidden_cols=True),
|
1070
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="tsv", copy_hidden_cols=False),
|
1071
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="tsv", copy_hidden_cols=True),
|
1072
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="csv", copy_hidden_cols=False),
|
1073
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="csv", copy_hidden_cols=True),
|
1074
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="custom_sv", copy_hidden_cols=False, separator=o),
|
1075
|
+
lambda items, indexed_items, selections, hidden_columns: copy_to_clipboard(items, indexed_items, selections, hidden_columns, representation="custom_sv", copy_hidden_cols=True, separator=o),
|
1076
|
+
]
|
1077
|
+
|
1078
|
+
# Copy items based on selection
|
1079
|
+
if s:
|
1080
|
+
for idx in s.keys():
|
1081
|
+
funcs[idx](self.items, self.indexed_items, self.selections, self.hidden_columns)
|
1082
|
+
|
1083
|
+
def save_dialog(self) -> None:
|
1084
|
+
|
1085
|
+
dump_header = []
|
1086
|
+
options = [
|
1087
|
+
["Save data (pickle)."],
|
1088
|
+
["Save data (csv)."],
|
1089
|
+
["Save data (tsv)."],
|
1090
|
+
["Save data (json)."],
|
1091
|
+
["Save data (feather)."],
|
1092
|
+
["Save data (parquet)."],
|
1093
|
+
["Save data (msgpack)."],
|
1094
|
+
["Save state"]
|
1095
|
+
]
|
1096
|
+
# require_option = [True, True, True, True, True, True, True, True]
|
1097
|
+
s, o, f = self.choose_option(self.stdscr, options=options, field_name="Save...", header=dump_header)
|
1098
|
+
|
1099
|
+
|
1100
|
+
funcs = [
|
1101
|
+
lambda opts: dump_data(self.get_function_data(), opts),
|
1102
|
+
lambda opts: dump_data(self.get_function_data(), opts, format="csv"),
|
1103
|
+
lambda opts: dump_data(self.get_function_data(), opts, format="tsv"),
|
1104
|
+
lambda opts: dump_data(self.get_function_data(), opts, format="json"),
|
1105
|
+
lambda opts: dump_data(self.get_function_data(), opts, format="feather"),
|
1106
|
+
lambda opts: dump_data(self.get_function_data(), opts, format="parquet"),
|
1107
|
+
lambda opts: dump_data(self.get_function_data(), opts, format="msgpack"),
|
1108
|
+
lambda opts: dump_state(self.get_function_data(), opts),
|
1109
|
+
]
|
1110
|
+
|
1111
|
+
if s:
|
1112
|
+
for idx in s.keys():
|
1113
|
+
save_path_entered, save_path = output_file_option_selector(
|
1114
|
+
self.stdscr,
|
1115
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1116
|
+
)
|
1117
|
+
if save_path_entered:
|
1118
|
+
return_val = funcs[idx](save_path)
|
1119
|
+
if return_val:
|
1120
|
+
self.notification(self.stdscr, message=return_val, title="Error")
|
1121
|
+
|
1122
|
+
def load_dialog(self) -> None:
|
1123
|
+
|
1124
|
+
dump_header = []
|
1125
|
+
options = [
|
1126
|
+
["Load data (pickle)."],
|
1127
|
+
]
|
1128
|
+
s, o, f = self.choose_option(self.stdscr, options=options, field_name="Open file...", header=dump_header)
|
1129
|
+
|
1130
|
+
|
1131
|
+
funcs = [
|
1132
|
+
lambda opts: load_state(opts)
|
1133
|
+
]
|
1134
|
+
|
1135
|
+
if s:
|
1136
|
+
file_to_load = file_picker()
|
1137
|
+
if file_to_load:
|
1138
|
+
index = list(s.keys())[0]
|
1139
|
+
return_val = funcs[index](file_to_load)
|
1140
|
+
self.set_function_data(return_val)
|
1141
|
+
|
1142
|
+
# items = return_val["items"]
|
1143
|
+
# header = return_val["header"]
|
1144
|
+
self.initialise_variables()
|
1145
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1146
|
+
self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
1147
|
+
# if return_val:
|
1148
|
+
# notification(stdscr, message=return_val, title="Error")
|
1149
|
+
|
1150
|
+
def set_registers(self):
|
1151
|
+
self.registers = {"*": self.indexed_items[self.cursor_pos][1][self.sort_column]} if len(self.indexed_items) and len(self.indexed_items[0][1]) else {}
|
1152
|
+
|
1153
|
+
|
1154
|
+
def run(self) -> Tuple[list[int], str, dict]:
|
1155
|
+
initial_time = time.time()-self.timer
|
1156
|
+
initial_time_footer = time.time()-self.footer_timer
|
1157
|
+
|
1158
|
+
self.initialise_variables(get_data=self.get_data_startup)
|
1159
|
+
|
1160
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1161
|
+
|
1162
|
+
if self.startup_notification:
|
1163
|
+
self.notification(self.stdscr, message=self.startup_notification)
|
1164
|
+
self.startup_notification = ""
|
1165
|
+
|
1166
|
+
curses.curs_set(0)
|
1167
|
+
# stdscr.nodelay(1) # Non-blocking input
|
1168
|
+
# stdscr.timeout(2000) # Set a timeout for getch() to ensure it does not block indefinitely
|
1169
|
+
self.stdscr.timeout(max(min(2000, int(self.timer*1000)), 20)) # Set a timeout for getch() to ensure it does not block indefinitely
|
1170
|
+
if self.clear_on_start:
|
1171
|
+
self.stdscr.clear()
|
1172
|
+
self.clear_on_start = False
|
1173
|
+
else:
|
1174
|
+
self.stdscr.erase()
|
1175
|
+
self.stdscr.refresh()
|
1176
|
+
|
1177
|
+
|
1178
|
+
|
1179
|
+
# Initialize colours
|
1180
|
+
# Check if terminal supports color
|
1181
|
+
if curses.has_colors() and self.colours != None:
|
1182
|
+
# raise Exception("Terminal does not support color")
|
1183
|
+
curses.start_color()
|
1184
|
+
colours_end = set_colours(pick=self.colour_theme_number, start=self.colours_start)
|
1185
|
+
|
1186
|
+
# Set terminal background color
|
1187
|
+
self.stdscr.bkgd(' ', curses.color_pair(self.colours_start+3)) # Apply background color
|
1188
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1189
|
+
|
1190
|
+
if self.display_only:
|
1191
|
+
self.stdscr.refresh()
|
1192
|
+
function_data = self.get_function_data()
|
1193
|
+
return [], "", function_data
|
1194
|
+
|
1195
|
+
# Main loop
|
1196
|
+
data_refreshed = False
|
1197
|
+
|
1198
|
+
while True:
|
1199
|
+
key = self.stdscr.getch()
|
1200
|
+
if key in self.disabled_keys: continue
|
1201
|
+
clear_screen=True
|
1202
|
+
|
1203
|
+
if self.check_key("refresh", key, self.keys_dict) or self.remapped_key(key, curses.KEY_F5, self.key_remappings) or (self.auto_refresh and (time.time() - initial_time) > self.timer):
|
1204
|
+
h, w = self.stdscr.getmaxyx()
|
1205
|
+
self.stdscr.addstr(0,w-3," ", curses.color_pair(self.colours_start+21) | curses.A_BOLD)
|
1206
|
+
self.stdscr.refresh()
|
1207
|
+
if self.get_new_data and self.refresh_function:
|
1208
|
+
self.initialise_variables(get_data=True)
|
1209
|
+
|
1210
|
+
initial_time = time.time()
|
1211
|
+
self.draw_screen(self.indexed_items, self.highlights, clear=False)
|
1212
|
+
else:
|
1213
|
+
|
1214
|
+
function_data = self.get_function_data()
|
1215
|
+
return [], "refresh", function_data
|
1216
|
+
|
1217
|
+
if self.footer_string_auto_refresh and ((time.time() - initial_time_footer) > self.footer_timer):
|
1218
|
+
self.footer_string = self.footer_string_refresh_function()
|
1219
|
+
initial_time_footer = time.time()
|
1220
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1221
|
+
|
1222
|
+
if self.check_key("help", key, self.keys_dict):
|
1223
|
+
self.stdscr.clear()
|
1224
|
+
self.stdscr.refresh()
|
1225
|
+
help_data = {
|
1226
|
+
"items": help_lines,
|
1227
|
+
"title": f"{self.title} Help",
|
1228
|
+
"colours_start": 150,
|
1229
|
+
"colours": help_colours,
|
1230
|
+
"show_footer": True,
|
1231
|
+
"max_selected": 1,
|
1232
|
+
"keys_dict": help_keys,
|
1233
|
+
"disabled_keys": [ord('?'), ord('v'), ord('V'), ord('m'), ord('M'), ord('l'), curses.KEY_ENTER, ord('\n')],
|
1234
|
+
"highlight_full_row": True,
|
1235
|
+
"top_gap": 0,
|
1236
|
+
"paginate": self.paginate,
|
1237
|
+
"centre_in_terminal": True,
|
1238
|
+
"centre_in_terminal_vertical": True,
|
1239
|
+
"hidden_columns": [],
|
1240
|
+
|
1241
|
+
}
|
1242
|
+
OptionPicker = Picker(self.stdscr, **help_data)
|
1243
|
+
s, o, f = OptionPicker.run()
|
1244
|
+
|
1245
|
+
elif self.check_key("exit", key, self.keys_dict):
|
1246
|
+
self.stdscr.clear()
|
1247
|
+
function_data = self.get_function_data()
|
1248
|
+
function_data["last_key"] = key
|
1249
|
+
return [], "", function_data
|
1250
|
+
elif self.check_key("full_exit", key, self.keys_dict):
|
1251
|
+
close_curses(self.stdscr)
|
1252
|
+
exit()
|
1253
|
+
|
1254
|
+
elif self.check_key("settings_input", key, self.keys_dict):
|
1255
|
+
usrtxt = f"{self.user_settings.strip()} " if self.user_settings else ""
|
1256
|
+
field_end_f = lambda: self.stdscr.getmaxyx()[1]-38 if self.show_footer else lambda: self.stdscr.getmaxyx()[1]-3
|
1257
|
+
if self.show_footer: field_end_f = lambda: self.stdscr.getmaxyx()[1]-38
|
1258
|
+
else: field_end_f = lambda: self.stdscr.getmaxyx()[1]-3
|
1259
|
+
self.set_registers()
|
1260
|
+
usrtxt, return_val = input_field(
|
1261
|
+
self.stdscr,
|
1262
|
+
usrtxt=usrtxt,
|
1263
|
+
field_name="Settings",
|
1264
|
+
x=lambda:2,
|
1265
|
+
y=lambda: self.stdscr.getmaxyx()[0]-1,
|
1266
|
+
max_length=field_end_f,
|
1267
|
+
registers=self.registers,
|
1268
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1269
|
+
)
|
1270
|
+
if return_val:
|
1271
|
+
self.user_settings = usrtxt
|
1272
|
+
self.apply_settings()
|
1273
|
+
self.user_settings = ""
|
1274
|
+
elif self.check_key("toggle_footer", key, self.keys_dict):
|
1275
|
+
self.user_settings = "footer"
|
1276
|
+
self.apply_settings()
|
1277
|
+
|
1278
|
+
elif self.check_key("settings_options", key, self.keys_dict):
|
1279
|
+
options = []
|
1280
|
+
if len(self.items) > 0:
|
1281
|
+
options += [["cv", "Centre rows vertically"]]
|
1282
|
+
options += [["ct", "Centre column-set in terminal"]]
|
1283
|
+
options += [["cc", "Centre values in cells"]]
|
1284
|
+
options += [["!r", "Toggle auto-refresh"]]
|
1285
|
+
options += [["th", "Cycle between themes"]]
|
1286
|
+
options += [["nohl", "Toggle highlights"]]
|
1287
|
+
options += [["footer", "Toggle footer"]]
|
1288
|
+
options += [[f"s{i}", f"Select col. {i}"] for i in range(len(self.items[0]))]
|
1289
|
+
options += [[f"!{i}", f"Toggle col. {i}"] for i in range(len(self.items[0]))]
|
1290
|
+
|
1291
|
+
settings_options_header = ["Key", "Setting"]
|
1292
|
+
|
1293
|
+
s, o, f = self.choose_option(self.stdscr, options=options, field_name="Settings", header=settings_options_header)
|
1294
|
+
if s:
|
1295
|
+
self.user_settings = " ".join([x[0] for x in s.values()])
|
1296
|
+
self.apply_settings()
|
1297
|
+
|
1298
|
+
# elif self.check_key("move_column_left", key, self.keys_dict):
|
1299
|
+
# tmp1 = self.column_indices[self.sort_column]
|
1300
|
+
# tmp2 = self.column_indices[(self.sort_column-1)%len(self.column_indices)]
|
1301
|
+
# self.column_indices[self.sort_column] = tmp2
|
1302
|
+
# self.column_indices[(self.sort_column-1)%(len(self.column_indices))] = tmp1
|
1303
|
+
# self.sort_column = (self.sort_column-1)%len(self.column_indices)
|
1304
|
+
# # self.notification(self.stdscr, f"{str(self.column_indices)}, {tmp1}, {tmp2}")
|
1305
|
+
# self.initialise_variables()
|
1306
|
+
# self.column_widths = get_column_widths([v[1] for v in self.indexed_items], header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns)
|
1307
|
+
# self.draw_screen(self.indexed_items, self.highlights)
|
1308
|
+
# # self.move_column(direction=-1)
|
1309
|
+
#
|
1310
|
+
# elif self.check_key("move_column_right", key, self.keys_dict):
|
1311
|
+
# tmp1 = self.column_indices[self.sort_column]
|
1312
|
+
# tmp2 = self.column_indices[(self.sort_column+1)%len(self.column_indices)]
|
1313
|
+
# self.column_indices[self.sort_column] = tmp2
|
1314
|
+
# self.column_indices[(self.sort_column+1)%(len(self.column_indices))] = tmp1
|
1315
|
+
# self.sort_column = (self.sort_column+1)%len(self.column_indices)
|
1316
|
+
# self.initialise_variables()
|
1317
|
+
# self.draw_screen(self.indexed_items, self.highlights)
|
1318
|
+
# # self.move_column(direction=1)
|
1319
|
+
|
1320
|
+
elif self.check_key("cursor_down", key, self.keys_dict):
|
1321
|
+
page_turned = self.cursor_down()
|
1322
|
+
if not page_turned: clear_screen = False
|
1323
|
+
elif self.check_key("half_page_down", key, self.keys_dict):
|
1324
|
+
clear_screen = False
|
1325
|
+
for i in range(self.items_per_page//2):
|
1326
|
+
if self.cursor_down(): clear_screen = True
|
1327
|
+
elif self.check_key("five_down", key, self.keys_dict):
|
1328
|
+
clear_screen = False
|
1329
|
+
for i in range(5):
|
1330
|
+
if self.cursor_down(): clear_screen = True
|
1331
|
+
elif self.check_key("cursor_up", key, self.keys_dict):
|
1332
|
+
page_turned = self.cursor_up()
|
1333
|
+
if not page_turned: clear_screen = False
|
1334
|
+
elif self.check_key("five_up", key, self.keys_dict):
|
1335
|
+
clear_screen = False
|
1336
|
+
for i in range(5):
|
1337
|
+
if self.cursor_up(): clear_screen = True
|
1338
|
+
elif self.check_key("half_page_up", key, self.keys_dict):
|
1339
|
+
clear_screen = False
|
1340
|
+
for i in range(self.items_per_page//2):
|
1341
|
+
if self.cursor_up(): clear_screen = True
|
1342
|
+
|
1343
|
+
elif self.check_key("toggle_select", key, self.keys_dict):
|
1344
|
+
if len(self.indexed_items) > 0:
|
1345
|
+
item_index = self.indexed_items[self.cursor_pos][0]
|
1346
|
+
selected_count = sum(self.selections.values())
|
1347
|
+
if self.max_selected == -1 or selected_count >= self.max_selected:
|
1348
|
+
self.toggle_item(item_index)
|
1349
|
+
self.cursor_down()
|
1350
|
+
elif self.check_key("select_all", key, self.keys_dict): # Select all (m or ctrl-a)
|
1351
|
+
self.select_all()
|
1352
|
+
|
1353
|
+
elif self.check_key("select_none", key, self.keys_dict): # Deselect all (M or ctrl-r)
|
1354
|
+
self.deselect_all()
|
1355
|
+
|
1356
|
+
elif self.check_key("cursor_top", key, self.keys_dict):
|
1357
|
+
new_pos = 0
|
1358
|
+
while True:
|
1359
|
+
if new_pos in self.unselectable_indices: new_pos+=1
|
1360
|
+
else: break
|
1361
|
+
if new_pos < len(self.indexed_items):
|
1362
|
+
self.cursor_pos = new_pos
|
1363
|
+
|
1364
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1365
|
+
|
1366
|
+
elif self.check_key("cursor_bottom", key, self.keys_dict):
|
1367
|
+
new_pos = len(self.indexed_items)-1
|
1368
|
+
while True:
|
1369
|
+
if new_pos in self.unselectable_indices: new_pos-=1
|
1370
|
+
else: break
|
1371
|
+
if new_pos < len(self.items) and new_pos >= 0:
|
1372
|
+
self.cursor_pos = new_pos
|
1373
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1374
|
+
# current_row = items_per_page - 1
|
1375
|
+
# if current_page + 1 == (len(self.indexed_items) + items_per_page - 1) // items_per_page:
|
1376
|
+
#
|
1377
|
+
# current_row = (len(self.indexed_items) +items_per_page - 1) % items_per_page
|
1378
|
+
# self.draw_screen(self.indexed_items, self.highlights)
|
1379
|
+
elif self.check_key("enter", key, self.keys_dict):
|
1380
|
+
# Print the selected indices if any, otherwise print the current index
|
1381
|
+
if self.is_selecting or self.is_deselecting: self.handle_visual_selection()
|
1382
|
+
if len(self.items) == 0:
|
1383
|
+
function_data = self.get_function_data()
|
1384
|
+
function_data["last_key"] = key
|
1385
|
+
return [], "", function_data
|
1386
|
+
selected_indices = get_selected_indices(self.selections)
|
1387
|
+
if not selected_indices:
|
1388
|
+
selected_indices = [self.indexed_items[self.cursor_pos][0]]
|
1389
|
+
|
1390
|
+
options_sufficient = True
|
1391
|
+
usrtxt = self.user_opts
|
1392
|
+
for index in selected_indices:
|
1393
|
+
if self.require_option[index]:
|
1394
|
+
if self.option_functions[index] != None:
|
1395
|
+
options_sufficient, usrtxt = self.option_functions[index](
|
1396
|
+
stdscr=self.stdscr,
|
1397
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1398
|
+
)
|
1399
|
+
else:
|
1400
|
+
self.set_registers()
|
1401
|
+
options_sufficient, usrtxt = default_option_input(
|
1402
|
+
self.stdscr,
|
1403
|
+
starting_value=self.user_opts,
|
1404
|
+
registers = self.registers
|
1405
|
+
)
|
1406
|
+
|
1407
|
+
if options_sufficient:
|
1408
|
+
self.user_opts = usrtxt
|
1409
|
+
self.stdscr.clear()
|
1410
|
+
self.stdscr.refresh()
|
1411
|
+
function_data = self.get_function_data()
|
1412
|
+
function_data["last_key"] = key
|
1413
|
+
return selected_indices, usrtxt, function_data
|
1414
|
+
elif self.check_key("page_down", key, self.keys_dict): # Next page
|
1415
|
+
self.cursor_pos = min(len(self.indexed_items) - 1, self.cursor_pos+self.items_per_page)
|
1416
|
+
|
1417
|
+
elif self.check_key("page_up", key, self.keys_dict):
|
1418
|
+
self.cursor_pos = max(0, self.cursor_pos-self.items_per_page)
|
1419
|
+
|
1420
|
+
elif self.check_key("redraw_screen", key, self.keys_dict):
|
1421
|
+
self.stdscr.clear()
|
1422
|
+
self.stdscr.refresh()
|
1423
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1424
|
+
|
1425
|
+
elif self.check_key("cycle_sort_method", key, self.keys_dict):
|
1426
|
+
self.columns_sort_method[self.sort_column] = (self.columns_sort_method[self.sort_column]+1) % len(self.SORT_METHODS)
|
1427
|
+
if len(self.indexed_items) > 0:
|
1428
|
+
current_index = self.indexed_items[self.cursor_pos][0]
|
1429
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1430
|
+
self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
|
1431
|
+
elif self.check_key("cycle_sort_method_reverse", key, self.keys_dict): # Cycle sort method
|
1432
|
+
self.columns_sort_method[self.sort_column] = (self.columns_sort_method[self.sort_column]-1) % len(self.SORT_METHODS)
|
1433
|
+
if len(self.indexed_items) > 0:
|
1434
|
+
current_index = self.indexed_items[self.cursor_pos][0]
|
1435
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1436
|
+
self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
|
1437
|
+
elif self.check_key("cycle_sort_order", key, self.keys_dict): # Toggle sort order
|
1438
|
+
self.sort_reverse[self.sort_column] = not self.sort_reverse[self.sort_column]
|
1439
|
+
if len(self.indexed_items) > 0:
|
1440
|
+
current_index = self.indexed_items[self.cursor_pos][0]
|
1441
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1442
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1443
|
+
self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
|
1444
|
+
elif self.check_key("col_select", key, self.keys_dict):
|
1445
|
+
col_index = key - ord('0')
|
1446
|
+
if 0 <= col_index < len(self.items[0]):
|
1447
|
+
self.sort_column = col_index
|
1448
|
+
if len(self.indexed_items) > 0:
|
1449
|
+
current_index = self.indexed_items[self.cursor_pos][0]
|
1450
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1451
|
+
self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
|
1452
|
+
elif self.check_key("col_select_next", key, self.keys_dict):
|
1453
|
+
if len(self.items) > 0 and len(self.items[0]) > 0:
|
1454
|
+
col_index = (self.sort_column +1) % (len(self.items[0]))
|
1455
|
+
self.sort_column = col_index
|
1456
|
+
if len(self.indexed_items) > 0:
|
1457
|
+
current_index = self.indexed_items[self.cursor_pos][0]
|
1458
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1459
|
+
self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
|
1460
|
+
elif self.check_key("col_select_prev", key, self.keys_dict):
|
1461
|
+
if len(self.items) > 0 and len(self.items[0]) > 0:
|
1462
|
+
col_index = (self.sort_column -1) % (len(self.items[0]))
|
1463
|
+
self.sort_column = col_index
|
1464
|
+
if len(self.indexed_items) > 0:
|
1465
|
+
current_index = self.indexed_items[self.cursor_pos][0]
|
1466
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1467
|
+
self.cursor_pos = [row[0] for row in self.indexed_items].index(current_index)
|
1468
|
+
elif self.check_key("col_hide", key, self.keys_dict):
|
1469
|
+
d = {'!': 0, '@': 1, '#': 2, '$': 3, '%': 4, '^': 5, '&': 6, '*': 7, '(': 8, ')': 9}
|
1470
|
+
d = {s:i for i,s in enumerate(")!@#$%^&*(")}
|
1471
|
+
col_index = d[chr(key)]
|
1472
|
+
self.toggle_column_visibility(col_index)
|
1473
|
+
elif self.check_key("copy", key, self.keys_dict):
|
1474
|
+
self.copy_dialog()
|
1475
|
+
elif self.check_key("save", key, self.keys_dict):
|
1476
|
+
self.save_dialog()
|
1477
|
+
elif self.check_key("load", key, self.keys_dict):
|
1478
|
+
self.load_dialog()
|
1479
|
+
|
1480
|
+
elif self.check_key("delete", key, self.keys_dict): # Delete key
|
1481
|
+
self.delete_entries()
|
1482
|
+
# elif self.check_key("increase_lines_per_page", key, self.keys_dict):
|
1483
|
+
# self.items_per_page += 1
|
1484
|
+
# self.draw_screen(self.indexed_items, self.highlights)
|
1485
|
+
# elif self.check_key("decrease_lines_per_page", key, self.keys_dict):
|
1486
|
+
# if self.items_per_page > 1:
|
1487
|
+
# self.items_per_page -= 1
|
1488
|
+
# self.draw_screen(self.indexed_items, self.highlights)
|
1489
|
+
elif self.check_key("decrease_column_width", key, self.keys_dict):
|
1490
|
+
if self.max_column_width > 10:
|
1491
|
+
self.max_column_width -= 10
|
1492
|
+
self.column_widths[:] = get_column_widths(self.items, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns)
|
1493
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1494
|
+
elif self.check_key("increase_column_width", key, self.keys_dict):
|
1495
|
+
if self.max_column_width < 1000:
|
1496
|
+
self.max_column_width += 10
|
1497
|
+
self.column_widths = get_column_widths(self.items, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns)
|
1498
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1499
|
+
elif self.check_key("visual_selection_toggle", key, self.keys_dict):
|
1500
|
+
self.handle_visual_selection()
|
1501
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1502
|
+
|
1503
|
+
elif self.check_key("visual_deselection_toggle", key, self.keys_dict):
|
1504
|
+
self.handle_visual_selection(selecting=False)
|
1505
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1506
|
+
|
1507
|
+
elif key == curses.KEY_RESIZE: # Terminal resize signal
|
1508
|
+
top_space = self.top_gap
|
1509
|
+
h, w = self.stdscr.getmaxyx()
|
1510
|
+
if self.title: top_space+=1
|
1511
|
+
if self.display_modes: top_space+=1
|
1512
|
+
|
1513
|
+
self.items_per_page = h - top_space-int(bool(self.header)) - 3*int(bool(self.show_footer))
|
1514
|
+
if not self.show_footer and self.footer_string: self.items_per_page-=1
|
1515
|
+
self.items_per_page = max(min(h-top_space-2, self.items_per_page), 0)
|
1516
|
+
self.column_widths = get_column_widths(self.items, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns)
|
1517
|
+
|
1518
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1519
|
+
|
1520
|
+
|
1521
|
+
elif key == ord('r'):
|
1522
|
+
# Refresh
|
1523
|
+
top_space = self.top_gap + int(bool(self.display_modes)) + int(bool(self.title)) + int(bool(self.header))
|
1524
|
+
bottom_space = 3*int(bool(self.show_footer))
|
1525
|
+
self.items_per_page = os.get_terminal_size().lines - top_space
|
1526
|
+
|
1527
|
+
h, w = self.stdscr.getmaxyx()
|
1528
|
+
self.items_per_page = h - top_space - bottom_space
|
1529
|
+
self.stdscr.refresh()
|
1530
|
+
|
1531
|
+
elif self.check_key("filter_input", key, self.keys_dict):
|
1532
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1533
|
+
usrtxt = f"{self.filter_query} " if self.filter_query else ""
|
1534
|
+
h, w = self.stdscr.getmaxyx()
|
1535
|
+
field_end_f = lambda: self.stdscr.getmaxyx()[1]-38 if self.show_footer else lambda: self.stdscr.getmaxyx()[1]-3
|
1536
|
+
if self.show_footer: field_end_f = lambda: self.stdscr.getmaxyx()[1]-38
|
1537
|
+
else: field_end_f = lambda: self.stdscr.getmaxyx()[1]-3
|
1538
|
+
self.set_registers()
|
1539
|
+
usrtxt, return_val = input_field(
|
1540
|
+
self.stdscr,
|
1541
|
+
usrtxt=usrtxt,
|
1542
|
+
field_name="Filter",
|
1543
|
+
x=lambda:2,
|
1544
|
+
y=lambda: self.stdscr.getmaxyx()[0]-2,
|
1545
|
+
# max_length=field_end,
|
1546
|
+
max_length=field_end_f,
|
1547
|
+
registers=self.registers,
|
1548
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1549
|
+
)
|
1550
|
+
if return_val:
|
1551
|
+
self.filter_query = usrtxt
|
1552
|
+
|
1553
|
+
# If the current mode filter has been changed then go back to the first mode
|
1554
|
+
if "filter" in self.modes[self.mode_index] and self.modes[self.mode_index]["filter"] not in self.filter_query:
|
1555
|
+
self.mode_index = 0
|
1556
|
+
# elif "filter" in modes[mode_index] and modes[mode_index]["filter"] in filter_query:
|
1557
|
+
# filter_query.split(modes[mode_index]["filter"])
|
1558
|
+
|
1559
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
1560
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
1561
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
1562
|
+
else: new_index = 0
|
1563
|
+
self.cursor_pos = new_index
|
1564
|
+
# Re-sort self.items after applying filter
|
1565
|
+
if self.columns_sort_method[self.sort_column] != 0:
|
1566
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1567
|
+
|
1568
|
+
elif self.check_key("search_input", key, self.keys_dict):
|
1569
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1570
|
+
usrtxt = f"{self.search_query} " if self.search_query else ""
|
1571
|
+
h, w = self.stdscr.getmaxyx()
|
1572
|
+
field_end_f = lambda: self.stdscr.getmaxyx()[1]-38 if self.show_footer else lambda: self.stdscr.getmaxyx()[1]-3
|
1573
|
+
if self.show_footer: field_end_f = lambda: self.stdscr.getmaxyx()[1]-38
|
1574
|
+
else: field_end_f = lambda: self.stdscr.getmaxyx()[1]-3
|
1575
|
+
self.set_registers()
|
1576
|
+
usrtxt, return_val = input_field(
|
1577
|
+
self.stdscr,
|
1578
|
+
usrtxt=usrtxt,
|
1579
|
+
field_name="Search",
|
1580
|
+
x=lambda:2,
|
1581
|
+
y=lambda: self.stdscr.getmaxyx()[0]-3,
|
1582
|
+
max_length=field_end_f,
|
1583
|
+
registers=self.registers,
|
1584
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1585
|
+
)
|
1586
|
+
if return_val:
|
1587
|
+
self.search_query = usrtxt
|
1588
|
+
return_val, tmp_cursor, tmp_index, tmp_count, tmp_highlights = search(
|
1589
|
+
query=self.search_query,
|
1590
|
+
indexed_items=self.indexed_items,
|
1591
|
+
highlights=self.highlights,
|
1592
|
+
cursor_pos=self.cursor_pos,
|
1593
|
+
unselectable_indices=self.unselectable_indices,
|
1594
|
+
)
|
1595
|
+
if return_val:
|
1596
|
+
self.cursor_pos, self.search_index, self.search_count, self.highlights = tmp_cursor, tmp_index, tmp_count, tmp_highlights
|
1597
|
+
else:
|
1598
|
+
self.search_index, self.search_count = 0, 0
|
1599
|
+
|
1600
|
+
elif self.check_key("continue_search_forward", key, self.keys_dict):
|
1601
|
+
return_val, tmp_cursor, tmp_index, tmp_count, tmp_highlights = search(
|
1602
|
+
query=self.search_query,
|
1603
|
+
indexed_items=self.indexed_items,
|
1604
|
+
highlights=self.highlights,
|
1605
|
+
cursor_pos=self.cursor_pos,
|
1606
|
+
unselectable_indices=self.unselectable_indices,
|
1607
|
+
continue_search=True,
|
1608
|
+
)
|
1609
|
+
if return_val:
|
1610
|
+
self.cursor_pos, self.search_index, self.search_count, self.highlights = tmp_cursor, tmp_index, tmp_count, tmp_highlights
|
1611
|
+
elif self.check_key("continue_search_backward", key, self.keys_dict):
|
1612
|
+
return_val, tmp_cursor, tmp_index, tmp_count, tmp_highlights = search(
|
1613
|
+
query=self.search_query,
|
1614
|
+
indexed_items=self.indexed_items,
|
1615
|
+
highlights=self.highlights,
|
1616
|
+
cursor_pos=self.cursor_pos,
|
1617
|
+
unselectable_indices=self.unselectable_indices,
|
1618
|
+
continue_search=True,
|
1619
|
+
reverse=True,
|
1620
|
+
)
|
1621
|
+
if return_val:
|
1622
|
+
self.cursor_pos, self.search_index, self.search_count, self.highlights = tmp_cursor, tmp_index, tmp_count, tmp_highlights
|
1623
|
+
elif self.check_key("cancel", key, self.keys_dict): # ESC key
|
1624
|
+
# order of escapes:
|
1625
|
+
# 1. selecting/deslecting
|
1626
|
+
# 2. search
|
1627
|
+
# 3. filter
|
1628
|
+
# 4. if self.cancel_is_back (e.g., notification) then we exit
|
1629
|
+
# 4. selecting
|
1630
|
+
|
1631
|
+
# Cancel visual de/selection
|
1632
|
+
if self.is_selecting or self.is_deselecting:
|
1633
|
+
self.start_selection = -1
|
1634
|
+
self.end_selection = -1
|
1635
|
+
self.is_selecting = False
|
1636
|
+
self.is_deselecting = False
|
1637
|
+
# Cancel search
|
1638
|
+
elif self.search_query:
|
1639
|
+
self.search_query = ""
|
1640
|
+
self.highlights = [highlight for highlight in self.highlights if "type" not in highlight or highlight["type"] != "search" ]
|
1641
|
+
# Remove filter
|
1642
|
+
elif self.filter_query:
|
1643
|
+
if "filter" in self.modes[self.mode_index] and self.modes[self.mode_index]["filter"] in self.filter_query and self.filter_query.strip() != self.modes[self.mode_index]["filter"]:
|
1644
|
+
self.filter_query = self.modes[self.mode_index]["filter"]
|
1645
|
+
# elif "filter" in modes[mode_index]:
|
1646
|
+
else:
|
1647
|
+
self.filter_query = ""
|
1648
|
+
self.mode_index = 0
|
1649
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
1650
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
1651
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
1652
|
+
else: new_index = 0
|
1653
|
+
self.cursor_pos = new_index
|
1654
|
+
# Re-sort self.items after applying filter
|
1655
|
+
if self.columns_sort_method[self.sort_column] != 0:
|
1656
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1657
|
+
elif self.cancel_is_back:
|
1658
|
+
function_data = self.get_function_data()
|
1659
|
+
function_data["last_key"] = key
|
1660
|
+
return [], "escape", function_data
|
1661
|
+
|
1662
|
+
|
1663
|
+
# else:
|
1664
|
+
# self.search_query = ""
|
1665
|
+
# self.mode_index = 0
|
1666
|
+
# self.highlights = [highlight for highlight in self.highlights if "type" not in highlight or highlight["type"] != "search" ]
|
1667
|
+
# continue
|
1668
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1669
|
+
|
1670
|
+
elif self.check_key("opts_input", key, self.keys_dict):
|
1671
|
+
usrtxt = f"{self.user_opts} " if self.user_opts else ""
|
1672
|
+
field_end_f = lambda: self.stdscr.getmaxyx()[1]-38 if self.show_footer else lambda: self.stdscr.getmaxyx()[1]-3
|
1673
|
+
if self.show_footer: field_end_f = lambda: self.stdscr.getmaxyx()[1]-38
|
1674
|
+
else: field_end_f = lambda: self.stdscr.getmaxyx()[1]-3
|
1675
|
+
self.set_registers()
|
1676
|
+
usrtxt, return_val = input_field(
|
1677
|
+
self.stdscr,
|
1678
|
+
usrtxt=usrtxt,
|
1679
|
+
field_name="Opts",
|
1680
|
+
x=lambda:2,
|
1681
|
+
y=lambda: self.stdscr.getmaxyx()[0]-1,
|
1682
|
+
max_length=field_end_f,
|
1683
|
+
registers=self.registers,
|
1684
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1685
|
+
)
|
1686
|
+
if return_val:
|
1687
|
+
self.user_opts = usrtxt
|
1688
|
+
elif self.check_key("opts_select", key, self.keys_dict):
|
1689
|
+
s, o, f = self.choose_option(self.stdscr)
|
1690
|
+
if self.user_opts.strip(): self.user_opts += " "
|
1691
|
+
self.user_opts += " ".join([x[0] for x in s.values()])
|
1692
|
+
elif self.check_key("notification_toggle", key, self.keys_dict):
|
1693
|
+
self.notification(self.stdscr, colours_end=self.colours_end)
|
1694
|
+
|
1695
|
+
elif self.check_key("mode_next", key, self.keys_dict): # tab key
|
1696
|
+
# apply setting
|
1697
|
+
prev_mode_index = self.mode_index
|
1698
|
+
self.mode_index = (self.mode_index+1)%len(self.modes)
|
1699
|
+
mode = self.modes[self.mode_index]
|
1700
|
+
for key, val in mode.items():
|
1701
|
+
if key == 'filter':
|
1702
|
+
if 'filter' in self.modes[prev_mode_index]:
|
1703
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
1704
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
1705
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
1706
|
+
|
1707
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
1708
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
1709
|
+
else: new_index = 0
|
1710
|
+
self.cursor_pos = new_index
|
1711
|
+
# Re-sort self.items after applying filter
|
1712
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1713
|
+
elif self.check_key("mode_prev", key, self.keys_dict): # shift+tab key
|
1714
|
+
# apply setting
|
1715
|
+
prev_mode_index = self.mode_index
|
1716
|
+
self.mode_index = (self.mode_index-1)%len(self.modes)
|
1717
|
+
mode = self.modes[self.mode_index]
|
1718
|
+
for key, val in mode.items():
|
1719
|
+
if key == 'filter':
|
1720
|
+
if 'filter' in self.modes[prev_mode_index]:
|
1721
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
1722
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
1723
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
1724
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
1725
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
1726
|
+
else: new_index = 0
|
1727
|
+
self.cursor_pos = new_index
|
1728
|
+
# Re-sort self.items after applying filter
|
1729
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
1730
|
+
elif self.check_key("pipe_input", key, self.keys_dict):
|
1731
|
+
usrtxt = "xargs -d '\n' -I{} "
|
1732
|
+
field_end_f = lambda: self.stdscr.getmaxyx()[1]-38 if self.show_footer else lambda: self.stdscr.getmaxyx()[1]-3
|
1733
|
+
if self.show_footer: field_end_f = lambda: self.stdscr.getmaxyx()[1]-38
|
1734
|
+
else: field_end_f = lambda: self.stdscr.getmaxyx()[1]-3
|
1735
|
+
self.set_registers()
|
1736
|
+
usrtxt, return_val = input_field(
|
1737
|
+
self.stdscr,
|
1738
|
+
usrtxt=usrtxt,
|
1739
|
+
field_name="Command",
|
1740
|
+
x=lambda:2,
|
1741
|
+
y=lambda: self.stdscr.getmaxyx()[0]-2,
|
1742
|
+
literal=True,
|
1743
|
+
max_length=field_end_f,
|
1744
|
+
registers=self.registers,
|
1745
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1746
|
+
)
|
1747
|
+
if return_val:
|
1748
|
+
selected_indices = get_selected_indices(self.selections)
|
1749
|
+
if not selected_indices:
|
1750
|
+
selected_indices = [self.indexed_items[self.cursor_pos][0]]
|
1751
|
+
|
1752
|
+
full_values = [format_row_full(self.items[i], self.hidden_columns) for i in selected_indices] # Use format_row_full for full data
|
1753
|
+
full_values = [self.items[i][self.sort_column] for i in selected_indices]
|
1754
|
+
if full_values:
|
1755
|
+
process = subprocess.Popen(usrtxt, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
1756
|
+
process.communicate(input='\n'.join(full_values).encode('utf-8'))
|
1757
|
+
|
1758
|
+
elif self.check_key("open", key, self.keys_dict):
|
1759
|
+
selected_indices = get_selected_indices(self.selections)
|
1760
|
+
if not selected_indices:
|
1761
|
+
selected_indices = [self.indexed_items[self.cursor_pos][0]]
|
1762
|
+
|
1763
|
+
file_names = [self.items[i][self.sort_column] for i in selected_indices]
|
1764
|
+
response = openFiles(file_names)
|
1765
|
+
if response:
|
1766
|
+
self.notification(self.stdscr, message=response)
|
1767
|
+
|
1768
|
+
|
1769
|
+
elif self.check_key("reset_opts", key, self.keys_dict):
|
1770
|
+
self.user_opts = ""
|
1771
|
+
|
1772
|
+
elif self.check_key("edit", key, self.keys_dict):
|
1773
|
+
if len(self.indexed_items) > 0 and self.sort_column >=0 and self.editable_columns[self.sort_column]:
|
1774
|
+
current_val = self.indexed_items[self.cursor_pos][1][self.sort_column]
|
1775
|
+
usrtxt = f"{current_val}"
|
1776
|
+
field_end_f = lambda: self.stdscr.getmaxyx()[1]-38 if self.show_footer else lambda: self.stdscr.getmaxyx()[1]-3
|
1777
|
+
if self.show_footer: field_end_f = lambda: self.stdscr.getmaxyx()[1]-38
|
1778
|
+
else: field_end_f = lambda: self.stdscr.getmaxyx()[1]-3
|
1779
|
+
self.set_registers()
|
1780
|
+
usrtxt, return_val = input_field(
|
1781
|
+
self.stdscr,
|
1782
|
+
usrtxt=usrtxt,
|
1783
|
+
field_name="Edit value",
|
1784
|
+
x=lambda:2,
|
1785
|
+
y=lambda: self.stdscr.getmaxyx()[0]-2,
|
1786
|
+
max_length=field_end_f,
|
1787
|
+
registers=self.registers,
|
1788
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1789
|
+
)
|
1790
|
+
if return_val:
|
1791
|
+
self.indexed_items[self.cursor_pos][1][self.sort_column] = usrtxt
|
1792
|
+
|
1793
|
+
elif self.check_key("edit_picker", key, self.keys_dict):
|
1794
|
+
if len(self.indexed_items) > 0 and self.sort_column >=0 and self.editable_columns[self.sort_column]:
|
1795
|
+
current_val = self.indexed_items[self.cursor_pos][1][self.sort_column]
|
1796
|
+
usrtxt = f"{current_val}"
|
1797
|
+
field_end_f = lambda: self.stdscr.getmaxyx()[1]-38 if self.show_footer else lambda: self.stdscr.getmaxyx()[1]-3
|
1798
|
+
if self.show_footer: field_end_f = lambda: self.stdscr.getmaxyx()[1]-38
|
1799
|
+
else: field_end_f = lambda: self.stdscr.getmaxyx()[1]-3
|
1800
|
+
self.set_registers()
|
1801
|
+
usrtxt, return_val = input_field(
|
1802
|
+
self.stdscr,
|
1803
|
+
usrtxt=usrtxt,
|
1804
|
+
field_name="Edit value",
|
1805
|
+
x=lambda:2,
|
1806
|
+
y=lambda: self.stdscr.getmaxyx()[0]-2,
|
1807
|
+
max_length=field_end_f,
|
1808
|
+
registers=self.registers,
|
1809
|
+
refresh_screen_function=lambda: self.draw_screen(self.indexed_items, self.highlights)
|
1810
|
+
)
|
1811
|
+
if return_val:
|
1812
|
+
self.indexed_items[self.cursor_pos][1][self.sort_column] = usrtxt
|
1813
|
+
elif self.check_key("edit_ipython", key, self.keys_dict):
|
1814
|
+
import IPython
|
1815
|
+
self.stdscr.clear()
|
1816
|
+
restrict_curses(self.stdscr)
|
1817
|
+
self.stdscr.clear()
|
1818
|
+
os.system('cls' if os.name == 'nt' else 'clear')
|
1819
|
+
globals()['self'] = self # make the instance available in IPython namespace
|
1820
|
+
|
1821
|
+
from traitlets.config import Config
|
1822
|
+
c = Config()
|
1823
|
+
# Doesn't work; Config only works with start_ipython, not embed... but start_ipython causes errors
|
1824
|
+
# c.InteractiveShellApp.exec_lines = [
|
1825
|
+
# '%clear'
|
1826
|
+
# ]
|
1827
|
+
msg = "The active Picker object has variable name self.\n"
|
1828
|
+
msg += "\te.g., self.items will display the items in Picker"
|
1829
|
+
IPython.embed(header=msg, config=c)
|
1830
|
+
|
1831
|
+
unrestrict_curses(self.stdscr)
|
1832
|
+
|
1833
|
+
self.stdscr.clear()
|
1834
|
+
self.stdscr.refresh()
|
1835
|
+
self.initialise_variables()
|
1836
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
1837
|
+
|
1838
|
+
|
1839
|
+
|
1840
|
+
self.draw_screen(self.indexed_items, self.highlights, clear=clear_screen)
|
1841
|
+
|
1842
|
+
|
1843
|
+
def set_colours(pick: int = 0, start: int = 0) -> Optional[int]:
|
1844
|
+
""" Initialise curses colour pairs using dictionary with colour keys. """
|
1845
|
+
global COLOURS_SET, notification_colours, help_colours
|
1846
|
+
if notification_colours == {}: notification_colours = get_notification_colours(0)
|
1847
|
+
if help_colours == {}: help_colours = get_help_colours(0)
|
1848
|
+
if COLOURS_SET: return None
|
1849
|
+
if start == None: start = 0
|
1850
|
+
|
1851
|
+
colours = get_colours(pick)
|
1852
|
+
notification_colours = get_notification_colours(pick)
|
1853
|
+
help_colours = get_help_colours(pick)
|
1854
|
+
|
1855
|
+
if not colours: return 0
|
1856
|
+
|
1857
|
+
try:
|
1858
|
+
curses.init_pair(start+1, colours['selected_fg'], colours['selected_bg'])
|
1859
|
+
curses.init_pair(start+2, colours['unselected_fg'], colours['unselected_bg'])
|
1860
|
+
curses.init_pair(start+3, colours['normal_fg'], colours['background'])
|
1861
|
+
curses.init_pair(start+4, colours['header_fg'], colours['header_bg'])
|
1862
|
+
curses.init_pair(start+5, colours['cursor_fg'], colours['cursor_bg'])
|
1863
|
+
curses.init_pair(start+6, colours['normal_fg'], colours['background'])
|
1864
|
+
curses.init_pair(start+7, colours['error_fg'], colours['error_bg'])
|
1865
|
+
curses.init_pair(start+8, colours['complete_fg'], colours['complete_bg'])
|
1866
|
+
curses.init_pair(start+9, colours['active_fg'], colours['active_bg'])
|
1867
|
+
curses.init_pair(start+10, colours['search_fg'], colours['search_bg'])
|
1868
|
+
curses.init_pair(start+11, colours['waiting_fg'], colours['waiting_bg'])
|
1869
|
+
curses.init_pair(start+12, colours['paused_fg'], colours['paused_bg'])
|
1870
|
+
curses.init_pair(start+13, colours['active_input_fg'], colours['active_input_bg'])
|
1871
|
+
curses.init_pair(start+14, colours['modes_selected_fg'], colours['modes_selected_bg'])
|
1872
|
+
curses.init_pair(start+15, colours['modes_unselected_fg'], colours['modes_unselected_bg'])
|
1873
|
+
curses.init_pair(start+16, colours['title_fg'], colours['title_bg'])
|
1874
|
+
curses.init_pair(start+17, colours['normal_fg'], colours['title_bar'])
|
1875
|
+
curses.init_pair(start+18, colours['normal_fg'], colours['scroll_bar_bg'])
|
1876
|
+
curses.init_pair(start+19, colours['selected_header_column_fg'], colours['selected_header_column_bg'])
|
1877
|
+
curses.init_pair(start+20, colours['footer_fg'], colours['footer_bg'])
|
1878
|
+
curses.init_pair(start+21, colours['refreshing_fg'], colours['refreshing_bg'])
|
1879
|
+
curses.init_pair(start+22, colours['40pc_fg'], colours['40pc_bg'])
|
1880
|
+
curses.init_pair(start+23, colours['refreshing_inactive_fg'], colours['refreshing_inactive_bg'])
|
1881
|
+
curses.init_pair(start+24, colours['footer_string_fg'], colours['footer_string_bg'])
|
1882
|
+
|
1883
|
+
|
1884
|
+
# notifications 50, infobox 100, help 150
|
1885
|
+
# Notification colours
|
1886
|
+
colours = notification_colours
|
1887
|
+
start = 50
|
1888
|
+
curses.init_pair(start+1, colours['selected_fg'], colours['selected_bg'])
|
1889
|
+
curses.init_pair(start+2, colours['unselected_fg'], colours['unselected_bg'])
|
1890
|
+
curses.init_pair(start+3, colours['normal_fg'], colours['background'])
|
1891
|
+
curses.init_pair(start+4, colours['header_fg'], colours['header_bg'])
|
1892
|
+
curses.init_pair(start+5, colours['cursor_fg'], colours['cursor_bg'])
|
1893
|
+
curses.init_pair(start+6, colours['normal_fg'], colours['background'])
|
1894
|
+
curses.init_pair(start+7, colours['error_fg'], colours['error_bg'])
|
1895
|
+
curses.init_pair(start+8, colours['complete_fg'], colours['complete_bg'])
|
1896
|
+
curses.init_pair(start+9, colours['active_fg'], colours['active_bg'])
|
1897
|
+
curses.init_pair(start+10, colours['search_fg'], colours['search_bg'])
|
1898
|
+
curses.init_pair(start+11, colours['waiting_fg'], colours['waiting_bg'])
|
1899
|
+
curses.init_pair(start+12, colours['paused_fg'], colours['paused_bg'])
|
1900
|
+
curses.init_pair(start+13, colours['active_input_fg'], colours['active_input_bg'])
|
1901
|
+
curses.init_pair(start+14, colours['modes_selected_fg'], colours['modes_selected_bg'])
|
1902
|
+
curses.init_pair(start+15, colours['modes_unselected_fg'], colours['modes_unselected_bg'])
|
1903
|
+
curses.init_pair(start+16, colours['title_fg'], colours['title_bg'])
|
1904
|
+
curses.init_pair(start+17, colours['normal_fg'], colours['title_bar'])
|
1905
|
+
curses.init_pair(start+18, colours['normal_fg'], colours['scroll_bar_bg'])
|
1906
|
+
curses.init_pair(start+19, colours['selected_header_column_fg'], colours['selected_header_column_bg'])
|
1907
|
+
curses.init_pair(start+20, colours['footer_fg'], colours['footer_bg'])
|
1908
|
+
curses.init_pair(start+21, colours['refreshing_fg'], colours['refreshing_bg'])
|
1909
|
+
curses.init_pair(start+22, colours['40pc_fg'], colours['40pc_bg'])
|
1910
|
+
|
1911
|
+
# Infobox
|
1912
|
+
colours = notification_colours
|
1913
|
+
start = 100
|
1914
|
+
curses.init_pair(start+1, colours['selected_fg'], colours['selected_bg'])
|
1915
|
+
curses.init_pair(start+2, colours['unselected_fg'], colours['unselected_bg'])
|
1916
|
+
curses.init_pair(start+3, colours['normal_fg'], colours['background'])
|
1917
|
+
curses.init_pair(start+4, colours['header_fg'], colours['header_bg'])
|
1918
|
+
curses.init_pair(start+5, colours['cursor_fg'], colours['cursor_bg'])
|
1919
|
+
curses.init_pair(start+6, colours['normal_fg'], colours['background'])
|
1920
|
+
curses.init_pair(start+7, colours['error_fg'], colours['error_bg'])
|
1921
|
+
curses.init_pair(start+8, colours['complete_fg'], colours['complete_bg'])
|
1922
|
+
curses.init_pair(start+9, colours['active_fg'], colours['active_bg'])
|
1923
|
+
curses.init_pair(start+10, colours['search_fg'], colours['search_bg'])
|
1924
|
+
curses.init_pair(start+11, colours['waiting_fg'], colours['waiting_bg'])
|
1925
|
+
curses.init_pair(start+12, colours['paused_fg'], colours['paused_bg'])
|
1926
|
+
curses.init_pair(start+13, colours['active_input_fg'], colours['active_input_bg'])
|
1927
|
+
curses.init_pair(start+14, colours['modes_selected_fg'], colours['modes_selected_bg'])
|
1928
|
+
curses.init_pair(start+15, colours['modes_unselected_fg'], colours['modes_unselected_bg'])
|
1929
|
+
curses.init_pair(start+16, colours['title_fg'], colours['title_bg'])
|
1930
|
+
curses.init_pair(start+17, colours['normal_fg'], colours['title_bar'])
|
1931
|
+
curses.init_pair(start+18, colours['normal_fg'], colours['scroll_bar_bg'])
|
1932
|
+
curses.init_pair(start+19, colours['selected_header_column_fg'], colours['selected_header_column_bg'])
|
1933
|
+
curses.init_pair(start+20, colours['footer_fg'], colours['footer_bg'])
|
1934
|
+
curses.init_pair(start+21, colours['refreshing_fg'], colours['refreshing_bg'])
|
1935
|
+
curses.init_pair(start+22, colours['40pc_fg'], colours['40pc_bg'])
|
1936
|
+
|
1937
|
+
# Help
|
1938
|
+
colours = help_colours
|
1939
|
+
start = 150
|
1940
|
+
curses.init_pair(start+1, colours['selected_fg'], colours['selected_bg'])
|
1941
|
+
curses.init_pair(start+2, colours['unselected_fg'], colours['unselected_bg'])
|
1942
|
+
curses.init_pair(start+3, colours['normal_fg'], colours['background'])
|
1943
|
+
curses.init_pair(start+4, colours['header_fg'], colours['header_bg'])
|
1944
|
+
curses.init_pair(start+5, colours['cursor_fg'], colours['cursor_bg'])
|
1945
|
+
curses.init_pair(start+6, colours['normal_fg'], colours['background'])
|
1946
|
+
curses.init_pair(start+7, colours['error_fg'], colours['error_bg'])
|
1947
|
+
curses.init_pair(start+8, colours['complete_fg'], colours['complete_bg'])
|
1948
|
+
curses.init_pair(start+9, colours['active_fg'], colours['active_bg'])
|
1949
|
+
curses.init_pair(start+10, colours['search_fg'], colours['search_bg'])
|
1950
|
+
curses.init_pair(start+11, colours['waiting_fg'], colours['waiting_bg'])
|
1951
|
+
curses.init_pair(start+12, colours['paused_fg'], colours['paused_bg'])
|
1952
|
+
curses.init_pair(start+13, colours['active_input_fg'], colours['active_input_bg'])
|
1953
|
+
curses.init_pair(start+14, colours['modes_selected_fg'], colours['modes_selected_bg'])
|
1954
|
+
curses.init_pair(start+15, colours['modes_unselected_fg'], colours['modes_unselected_bg'])
|
1955
|
+
curses.init_pair(start+16, colours['title_fg'], colours['title_bg'])
|
1956
|
+
curses.init_pair(start+17, colours['normal_fg'], colours['title_bar'])
|
1957
|
+
curses.init_pair(start+18, colours['normal_fg'], colours['scroll_bar_bg'])
|
1958
|
+
curses.init_pair(start+19, colours['selected_header_column_fg'], colours['selected_header_column_bg'])
|
1959
|
+
curses.init_pair(start+20, colours['footer_fg'], colours['footer_bg'])
|
1960
|
+
curses.init_pair(start+21, colours['refreshing_fg'], colours['refreshing_bg'])
|
1961
|
+
curses.init_pair(start+22, colours['40pc_fg'], colours['40pc_bg'])
|
1962
|
+
except:
|
1963
|
+
pass
|
1964
|
+
COLOURS_SET = True
|
1965
|
+
return start+21
|
1966
|
+
|
1967
|
+
def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
1968
|
+
""" Parse arguments. """
|
1969
|
+
parser = argparse.ArgumentParser(description='Convert table to list of lists.')
|
1970
|
+
parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
|
1971
|
+
parser.add_argument('--load', '-l', dest='load', type=str, help='File load from list_picker dump.')
|
1972
|
+
parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
|
1973
|
+
parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
|
1974
|
+
parser.add_argument('--generate', '-g', type=str, help='Pass file to generate data for list picker.')
|
1975
|
+
parser.add_argument('-d', dest='delimiter', default='\t', help='Delimiter for rows in the table (default: tab)')
|
1976
|
+
parser.add_argument('-t', dest='file_type', choices=['tsv', 'csv', 'json', 'xlsx', 'ods', 'pkl'], help='Type of file (tsv, csv, json, xlsx, ods)')
|
1977
|
+
args = parser.parse_args()
|
1978
|
+
|
1979
|
+
function_data = {
|
1980
|
+
"items" : [],
|
1981
|
+
"header": [],
|
1982
|
+
"unselectable_indices" : [],
|
1983
|
+
"colours": get_colours(0),
|
1984
|
+
"top_gap": 0,
|
1985
|
+
"max_column_width": 70,
|
1986
|
+
}
|
1987
|
+
|
1988
|
+
if args.file:
|
1989
|
+
input_arg = args.file
|
1990
|
+
elif args.stdin:
|
1991
|
+
input_arg = '--stdin'
|
1992
|
+
elif args.stdin2:
|
1993
|
+
input_arg = '--stdin2'
|
1994
|
+
|
1995
|
+
elif args.generate:
|
1996
|
+
function_data["refresh_function"] = lambda : generate_list_picker_data(args.generate)
|
1997
|
+
function_data["get_data_startup"] = True
|
1998
|
+
function_data["get_new_data"] = True
|
1999
|
+
return args, function_data
|
2000
|
+
elif args.load:
|
2001
|
+
function_data = load_state(args.load)
|
2002
|
+
function_data["refresh_function"] = lambda : (load_state(args.load)["items"], load_state(args.load)["header"])
|
2003
|
+
function_data["get_new_data"] = True
|
2004
|
+
return args, function_data
|
2005
|
+
|
2006
|
+
else:
|
2007
|
+
print("Error: Please provide input file or use --stdin flag.")
|
2008
|
+
return args, function_data
|
2009
|
+
# sys.exit(1)
|
2010
|
+
|
2011
|
+
items, header = table_to_list(input_arg, args.delimiter, args.file_type)
|
2012
|
+
function_data["items"] = items
|
2013
|
+
if header: function_data["header"] = header
|
2014
|
+
return args, function_data
|
2015
|
+
|
2016
|
+
def start_curses() -> curses.window:
|
2017
|
+
stdscr = curses.initscr()
|
2018
|
+
curses.start_color()
|
2019
|
+
curses.noecho() # Turn off automatic echoing of keys to the screen
|
2020
|
+
curses.cbreak() # Interpret keystrokes immediately (without requiring Enter)
|
2021
|
+
stdscr.keypad(True)
|
2022
|
+
curses.raw()
|
2023
|
+
|
2024
|
+
return stdscr
|
2025
|
+
|
2026
|
+
def close_curses(stdscr: curses.window) -> None:
|
2027
|
+
stdscr.keypad(False)
|
2028
|
+
curses.nocbreak()
|
2029
|
+
curses.noraw()
|
2030
|
+
curses.echo()
|
2031
|
+
curses.endwin()
|
2032
|
+
|
2033
|
+
def restrict_curses(stdscr: curses.window) -> None:
|
2034
|
+
stdscr.keypad(False)
|
2035
|
+
curses.nocbreak()
|
2036
|
+
curses.noraw()
|
2037
|
+
curses.echo()
|
2038
|
+
|
2039
|
+
def unrestrict_curses(stdscr: curses.window) -> None:
|
2040
|
+
curses.noecho() # Turn off automatic echoing of keys to the screen
|
2041
|
+
curses.cbreak() # Interpret keystrokes immediately (without requiring Enter)
|
2042
|
+
stdscr.keypad(True)
|
2043
|
+
|
2044
|
+
def main():
|
2045
|
+
args, function_data = parse_arguments()
|
2046
|
+
|
2047
|
+
try:
|
2048
|
+
if function_data["items"] == []:
|
2049
|
+
function_data["items"] = test_items
|
2050
|
+
function_data["highlights"] = test_highlights
|
2051
|
+
function_data["header"] = test_header
|
2052
|
+
except:
|
2053
|
+
pass
|
2054
|
+
|
2055
|
+
stdscr = start_curses()
|
2056
|
+
try:
|
2057
|
+
# Run the list picker
|
2058
|
+
h, w = stdscr.getmaxyx()
|
2059
|
+
if (h>8 and w >20):
|
2060
|
+
curses.init_pair(1, 253, 232)
|
2061
|
+
stdscr.bkgd(' ', curses.color_pair(1)) # Apply background color
|
2062
|
+
stdscr.addstr(h//2, (w-len("List picker is loading your data..."))//2, "List picker is loading your data...")
|
2063
|
+
stdscr.refresh()
|
2064
|
+
|
2065
|
+
app = Picker(stdscr, **function_data)
|
2066
|
+
app.run()
|
2067
|
+
except Exception as e:
|
2068
|
+
print(e)
|
2069
|
+
|
2070
|
+
# Clean up
|
2071
|
+
close_curses(stdscr)
|
2072
|
+
|
2073
|
+
|
2074
|
+
|
2075
|
+
if __name__ == '__main__':
|
2076
|
+
main()
|