1+ import curses
12from curses import wrapper , use_default_colors , curs_set
23from typing import List
34from pymenu .misc import Item , Keyboard
@@ -7,40 +8,40 @@ class SelectorMenu:
78
89 def __init__ (self , options : List [str ], title : str = '' , default_index : int = 0 , indicator : str = "->" ):
910 if len (options ) == 0 :
10- raise ValueError ('must contains 1 element' )
11+ raise Exception ('must contains 1 element' )
1112 if 0 <= default_index >= len (options ):
1213 raise ValueError ('default_index should be less than the length of options' )
1314
14- self .__options = options
15- self .__title = title
16- self .__index = default_index
17- self .__indicator = indicator
18- self .__scroll_top = 0
15+ self ._options = options
16+ self ._title = title
17+ self ._index = default_index
18+ self ._indicator = indicator
19+ self ._scroll_top = 0
1920
2021 def get_selected (self ) -> Item :
21- return Item (name = self .__options [self .__index ], index = self .__index )
22+ return Item (name = self ._options [self ._index ], index = self ._index )
2223
2324 def get_option_lines (self ):
2425 lines = []
25- for index , option in enumerate (self .__options ):
26- if index == self .__index :
27- prefix = self .__indicator
26+ for index , option in enumerate (self ._options ):
27+ if index == self ._index :
28+ prefix = self ._indicator
2829 else :
29- prefix = len (self .__indicator ) * ' '
30+ prefix = len (self ._indicator ) * ' '
3031 lines .append (f'{ prefix } { option } ' )
3132 return lines
3233
3334 def get_lines (self ):
34- title_lines = self .__title .split ('\n ' )
35- current_line_y = self .__index + len (title_lines ) + 1
35+ title_lines = self ._title .split ('\n ' )
36+ current_line_y = self ._index + len (title_lines ) + 1
3637 lines = title_lines + self .get_option_lines ()
3738 return lines , current_line_y
3839
3940 def go_up (self ):
40- self .__index = (self .__index - 1 ) % len (self .__options )
41+ self ._index = (self ._index - 1 ) % len (self ._options )
4142
4243 def go_down (self ):
43- self .__index = (self .__index + 1 ) % len (self .__options )
44+ self ._index = (self ._index + 1 ) % len (self ._options )
4445
4546 def draw (self , screen ) -> None :
4647 screen .clear ()
@@ -49,12 +50,12 @@ def draw(self, screen) -> None:
4950
5051 lines , current_line = self .get_lines ()
5152
52- if current_line <= self .__scroll_top :
53- self .__scroll_top = 0
54- elif current_line - self .__scroll_top > max_y :
55- self .__scroll_top = current_line - max_y
53+ if current_line <= self ._scroll_top :
54+ self ._scroll_top = 0
55+ elif current_line - self ._scroll_top > max_y :
56+ self ._scroll_top = current_line - max_y
5657
57- lines_to_draw = lines [self .__scroll_top :self .__scroll_top + max_y ]
58+ lines_to_draw = lines [self ._scroll_top :self ._scroll_top + max_y ]
5859
5960 for y , line in enumerate (lines_to_draw ):
6061 screen .addnstr (y , 0 , line , max_x - 2 )
@@ -88,3 +89,69 @@ def __start(self, screen):
8889
8990 def input (self ):
9091 return wrapper (self .__start )
92+
93+
94+ class MultiSelectorMenu (SelectorMenu ):
95+
96+ def __init__ (self , options : List [str ], count : int , title : str = '' , default_index : int = 0 , indicator : str = "->" ):
97+ if 1 <= count > len (options ):
98+ raise Exception
99+ self .count = count
100+ self .selected : list [int ] = []
101+ super ().__init__ (options , title , default_index , indicator )
102+
103+ def run_loop (self , screen ):
104+ config = {
105+ Keyboard .UP : self .go_up ,
106+ Keyboard .DOWN : self .go_down ,
107+ Keyboard .SELECT : self .select
108+ }
109+ returnable_config = {
110+ Keyboard .ENTER : self .get_selected ,
111+ }
112+ while True :
113+ self .draw (screen )
114+ key = screen .getch ()
115+ for keys in config .keys ():
116+ if key in keys :
117+ config [keys ]()
118+ break
119+ for keys in returnable_config .keys ():
120+ if key in keys :
121+ return returnable_config [keys ]()
122+
123+ def select (self ):
124+ if self ._index in self .selected :
125+ self .selected .remove (self ._index )
126+ return
127+ if len (self .selected ) < self .count :
128+ self .selected .append (self ._index )
129+
130+ def get_selected (self ) -> list [Item ]:
131+ self .selected .sort ()
132+ return [Item (index = i , name = self ._options [i ]) for i in self .selected ]
133+
134+ def draw (self , screen ) -> None :
135+ curses .init_pair (1 , curses .COLOR_BLACK , curses .COLOR_WHITE )
136+
137+ screen .clear ()
138+
139+ max_y , max_x = screen .getmaxyx ()
140+
141+ lines , current_line = self .get_lines ()
142+
143+ if current_line <= self ._scroll_top :
144+ self ._scroll_top = 0
145+ elif current_line - self ._scroll_top > max_y :
146+ self ._scroll_top = current_line - max_y
147+
148+ lines_to_draw = lines [self ._scroll_top :self ._scroll_top + max_y ]
149+
150+ for y , line in enumerate (lines_to_draw ):
151+ if y - len (self ._title .split ('\n ' )) in self .selected :
152+ screen .addnstr (y , 0 , line , max_x - 2 , curses .color_pair (1 ))
153+ continue
154+ screen .addnstr (y , 0 , line , max_x - 2 )
155+ y += 1
156+
157+ screen .refresh ()
0 commit comments