33import os
44import webbrowser
55from pathlib import Path
6+ from tkinter .simpledialog import askstring
67from typing import Any , Callable , Literal , Optional , TypeAlias , Union
78
89from roa import RoaCategoriesFile , RoaCategory , RoaEntry , RoaOrderFile
1617
1718Direction : TypeAlias = Union [Literal [1 ], Literal [- 1 ]]
1819
20+
1921@dataclass
2022class ListboxItem ():
2123 label : str
2224 value : Any
2325
26+
2427class Counter ():
2528 def __init__ (self , value = 0 ):
2629 self .value = value
@@ -30,34 +33,35 @@ def inc(self):
3033 self .value += 1
3134 return last_val
3235
36+
3337def sort_name (entry : RoaEntry ):
3438 try :
3539 return entry .name .upper ()
3640 except :
3741 return 'ERROR'
3842
43+
3944class ItemListFrame (tk .Frame ):
4045 def __init__ (
4146 self ,
4247 parent ,
4348 selectmode = tk .SINGLE
4449 ) -> None :
45- super ().__init__ (parent , bg = "yellow" )
46-
50+ super ().__init__ (parent )
4751 self .items : list [ListboxItem ] = []
4852
4953 myscroll = tk .Scrollbar (self )
50- myscroll .pack (side = tk .RIGHT , fill = tk .Y )
54+ myscroll .pack (side = tk .RIGHT , fill = tk .Y )
5155
5256 self .listbox = tk .Listbox (
5357 self ,
5458 relief = tk .GROOVE ,
5559 selectmode = selectmode ,
5660 exportselection = False ,
57- yscrollcommand = myscroll .set
61+ yscrollcommand = myscroll .set
5862 )
5963
60- myscroll .config (command = self .listbox .yview )
64+ myscroll .config (command = self .listbox .yview )
6165
6266 self .listbox .pack (side = tk .TOP , fill = tk .Y , expand = 1 )
6367
@@ -87,23 +91,23 @@ def move_selected_items(self, sel_indexes, direction: Direction) -> list[Listbox
8791 return self .items
8892
8993
90- class CharacterManager (tk .Tk ):
94+ class CharacterManager (tk .Tk ): # noqa: PLR0904
9195 def __init__ (
9296 self ,
9397 order_roa : RoaOrderFile ,
9498 categories_roa : RoaCategoriesFile
9599 ) -> None :
96100 super ().__init__ ()
101+ self .title ("Re-ROAder" )
97102
98103 self .text_status : tk .StringVar = tk .StringVar (value = "Status" )
99104 self .initwindow ()
100105
101106 self .order_roa : RoaOrderFile = order_roa
102107 self .categories_roa : RoaCategoriesFile = categories_roa
103108
104- self .nested_state : dict [str , list [RoaEntry ]] = roa_zip_chars (order_roa , categories_roa )
105-
106- self .load_roa_state ()
109+ self .load_state_from_roa ()
110+ self .load_gui_from_state ()
107111
108112 self .mainloop ()
109113
@@ -112,12 +116,20 @@ def log(self, line) -> None:
112116 print (line )
113117 self .text_status .set (line )
114118
119+ def load_state_from_roa (self ):
120+ self .nested_state : dict [str , list [RoaEntry ]] = roa_zip_chars (order_roa , categories_roa )
121+
115122 # Widget management
116123
117124 def widget_buttons_middle (self ) -> tk .Frame :
118125 frame = tk .Frame (self )
119126 y = Counter ()
120127
128+ btn_export = ttk .Button (
129+ frame , text = "Reload discarding changes" ,
130+ command = self .load_state_from_roa )
131+ btn_export .grid (row = y .inc (), sticky = tk .EW )
132+
121133 btn_export = ttk .Button (
122134 frame , text = "Export to ROA" ,
123135 command = self .save_state_to_roas )
@@ -142,13 +154,14 @@ def widget_buttons_cats(self) -> tk.Frame:
142154
143155 frame_updown .grid (row = y .inc (), sticky = tk .EW )
144156
145- btn_add = ttk .Button (frame , text = "TODO Add" )
157+ btn_add = ttk .Button (frame , text = "Add" , command = self . add_category )
146158 btn_add .grid (row = y .inc (), sticky = tk .EW )
147159
148- btn_del = ttk .Button (frame , text = "TODO Delete" )
160+ btn_del = ttk .Button (frame , text = "Delete" , command = self . delete_category )
149161 btn_del .grid (row = y .inc (), sticky = tk .EW )
150162
151- btn_rename = ttk .Button (frame , text = "TODO Rename" )
163+ btn_rename = ttk .Button (frame , text = "Rename" ,
164+ command = self .interactive_rename_category )
152165 btn_rename .grid (row = y .inc (), sticky = tk .EW )
153166
154167 return frame
@@ -184,15 +197,21 @@ def widget_buttons_chars(self) -> tk.Frame:
184197 )
185198 btn_char_info .grid (row = y .inc (), sticky = tk .EW )
186199
187- btn_char_movecat = ttk .Button (
188- frame , text = "TODO Move to..." ,
189- # command=
190- )
191- btn_char_movecat .grid (row = y .inc (), sticky = tk .EW )
200+ # btn_char_movecat = ttk.Button(
201+ # frame, text="TODO Move to...",
202+ # # command=
203+ # )
204+ # btn_char_movecat.grid(row=y.inc(), sticky=tk.EW)
205+
206+ self .combo_cats = ttk .Combobox (frame )
207+ self .combo_cats .bind ("<<ComboboxSelected>>" , self .move_chars_to_combobox_cat )
208+ self .combo_cats .set ("Move to category..." )
209+ self .combo_cats .grid (row = y .inc (), sticky = tk .EW )
210+
192211 return frame
193212
194213 def initwindow (self ) -> None :
195- self .geometry ("860x600 " )
214+ self .geometry ("500x600 " )
196215
197216 self .grid_rowconfigure (0 , weight = 1 )
198217 self .grid_rowconfigure (1 , weight = 0 )
@@ -226,12 +245,15 @@ def gen_listitems_categories(self) -> list[ListboxItem]:
226245 for category , chars in self .nested_state .items ()
227246 ]
228247
229- def load_roa_state (self ):
248+ def load_gui_from_state (self ):
230249 category_items : list [ListboxItem ] = self .gen_listitems_categories ()
231250 self .list_cats .set_items (category_items )
232251 self .log (f"Loaded { len (category_items )} categories" )
233252
234253 self .list_cats .listbox .selection_set (0 )
254+
255+ self .combo_cats .configure (values = [c .label for c in category_items ])
256+
235257 self .open_selected_category ()
236258
237259 def save_state_to_roas (self , event = None ) -> None :
@@ -242,8 +264,10 @@ def save_state_to_roas(self, event=None) -> None:
242264 if len (char_list ) < 1 :
243265 continue
244266 new_cat = RoaCategory (len (characters ), label .encode ('utf-8' ))
267+ print (new_cat )
245268 categories_roa .categories .append (new_cat )
246269 for char in char_list :
270+ print (char )
247271 try :
248272 characters .append (char )
249273 except KeyError :
@@ -280,6 +304,11 @@ def open_category(self, category: str) -> None:
280304 self .list_chars .set_items (group_items )
281305 self .log (f"Loaded { len (group_items )} chars from group { category !r} " )
282306
307+ # Update UI in case category was opened programatically
308+ self .list_cats .listbox .select_clear (0 , tk .END )
309+ cat_index = [i .value for i in self .list_cats .items ].index (category )
310+ self .list_cats .listbox .selection_set (cat_index )
311+
283312 def fac_sort_chars_by (self , key_fn ) -> Callable [..., None ]:
284313 def do_sort (event = None ):
285314 category = self .get_selected_category ()
@@ -323,6 +352,59 @@ def open_info(self, event=None):
323352 url = f"steam://openurl/https://steamcommunity.com/sharedfiles/filedetails/?id={ char .id } "
324353 webbrowser .open (url , autoraise = True )
325354
355+ def move_char_to_category (self , src_cat : str , dest_cat : str , char : RoaEntry ):
356+ self .log (f"Moving { char } from { src_cat } to { dest_cat } " )
357+ self .nested_state [src_cat ].remove (char )
358+ self .nested_state [dest_cat ].append (char )
359+
360+ self .load_gui_from_state ()
361+
362+ def interactive_rename_category (self ):
363+ cur_cat = self .get_selected_category ()
364+ new_name = askstring (title = None , prompt = f"New name for { cur_cat !r} " )
365+ if new_name :
366+ self .rename_category (cur_cat , new_name )
367+ self .open_category (new_name )
368+
369+ def rename_category (self , cat : str , new_name : str ):
370+ tups = list (self .nested_state .items ())
371+ for i , t in enumerate (tups ):
372+ label , charlist = t
373+ if label == cat :
374+ tups [i ] = (new_name , charlist )
375+ break
376+ else :
377+ self .log (f"Couldn't find { cat } in { tups } " )
378+ self .nested_state = OrderedDict (tups )
379+
380+ self .load_gui_from_state ()
381+
382+ def delete_category (self ):
383+ cat_name = self .get_selected_category ()
384+ if cat_name and len (self .nested_state [cat_name ]) == 0 :
385+ self .nested_state .pop (cat_name )
386+ self .load_gui_from_state ()
387+ else :
388+ self .log ("Can only remove empty categories" )
389+
390+ def add_category (self ):
391+ new_name = askstring (title = None , prompt = f"Name for new category" )
392+ if new_name and new_name not in self .nested_state .keys ():
393+ self .nested_state [new_name ] = []
394+ self .load_gui_from_state ()
395+ self .open_category (new_name )
396+
397+ def move_chars_to_combobox_cat (self , event = None ):
398+ self .log (event )
399+ src_cat = self .get_selected_category ()
400+ dest_cat_label = self .combo_cats .get ()
401+ dest_cat = {c .label : c .value for c in self .gen_listitems_categories ()}[dest_cat_label ]
402+ for sel_index in self .list_chars .listbox .curselection ():
403+ char = self .list_chars .items [sel_index ].value
404+ self .move_char_to_category (src_cat , dest_cat , char )
405+
406+ self .open_category (src_cat )
407+ self .combo_cats .set ("Move to category..." )
326408
327409if __name__ == '__main__' :
328410 order_roa = RoaOrderFile (ROA_DIR / 'order.roa' )
0 commit comments