-
Notifications
You must be signed in to change notification settings - Fork 1
/
tk_gui.py
353 lines (304 loc) · 14.8 KB
/
tk_gui.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
import logging
import tkinter as tk
from tkinter import StringVar
from tkinter.messagebox import askyesno
from tkinter import messagebox
from tkinter import simpledialog
import traceback
import json
from abstract_classes import UserInterface, UserInput, UserAction, ParsingReturnValues, ViewTypes
from model import Model, GameCheat
import queue
# partial to invoke callback with arguments
from functools import partial
class GUI(UserInterface):
def __init__(self, callback, model: Model, test: bool = False):
self.TEST = test
self.type = ViewTypes.TKINTER_GUI
self.error = None
self.traceback = None
self.root = None
self.addresses_entries_frame = None
self.select_game_menu = None
self.opt_menu = None
self.add_cheatcode_option_string = "--Add New Cheatcode--"
self.add_game_option_string = "--Add Game--"
self.select_cheatcode = None
self.cheatcode_opt_menu = None
self.state = UserAction.NO_ACTION
self.callback = callback
self.model = model
self.model_data = json.loads(model.get_games_as_json())
self.no_data_actions = [UserAction.SHOW_ALL_DATA_FROM_AR,
UserAction.PRINT_GAME,
UserAction.NO_ACTION,
UserAction.END_PROGRAM]
def create_info_field(self, height: int, width: int, row: int, column: int) -> tk.Entry:
T = tk.Entry(self.root, width=width)
T.insert(tk.END,"")
T.grid(row=row,column=column)
return T
@staticmethod
def create_cheatcodes_frame(master: tk.Misc, row: int, column: int) -> tk.Frame:
frame = tk.Frame(master=master)
frame.grid(row=row, column=column)
for num_of_entry in range(10): # TODO define constant?
entry = tk.Entry(master=frame, width=50)
entry.insert(tk.END, "")
entry.pack()
return frame
@staticmethod
def get_userinput_string(string: str) -> str:
new_name = ''
while new_name == '':
new_name = simpledialog.askstring("User Input", string)
return new_name
def adding_cheatcode_to_menu(self, cheatcodename: str):
self.select_cheatcode.set(cheatcodename)
self.cheatcode_opt_menu["menu"].add_command(
label=cheatcodename,
command=tk._setit(
self.select_cheatcode,
cheatcodename,
callback=partial(lambda x: self.__update_cheatcode_entries_on_select())
)
)
def adding_game_to_menu(self, gamename: str):
self.clear_gui()
self.select_game_menu.set(gamename)
self.opt_menu["menu"].add_command(
label=gamename,
command=tk._setit(
self.select_game_menu,
gamename,
callback=self.update_cheatcode_menu # TODO replace with self.handle_game_option_selection
)
)
def __update_cheatcode_entries_on_select(self):
self.clear_gui()
selected_game_name = self.select_game_menu.get()
selected_cheat_code_name = self.select_cheatcode.get()
if selected_cheat_code_name == self.add_cheatcode_option_string:
# pop dialog up for new cheat code name
selected_cheat_code_name = self.get_userinput_string("Type in the name of your new cheatcode")
# Adding new cheatcode option to option menu
self.adding_cheatcode_to_menu(selected_cheat_code_name)
addresses = []
# Note: If the new entry is not directly added to the model, a KeyValue Exception will be thrown
# add directly to the model
self.prepare_and_exec_callback(UserAction.MODIFY_DATA)
else:
cheat_codes = self.model_data[selected_game_name]
addresses = cheat_codes[selected_cheat_code_name]
self.insert_into_gui(
selected_game_name,
selected_cheat_code_name,
addresses
)
def handle_game_option_selection(self, option):
game_name = self.select_game_menu.get()
if game_name == self.add_game_option_string:
game_name = self.get_userinput_string("Type in the name of your new game")
self.adding_game_to_menu(game_name)
self.prepare_and_exec_callback(UserAction.ADD_NEW_GAME)
self.select_game_menu.set(game_name)
self.update_cheatcode_menu(option)
def create_cheatcode_option_menu(self, master: tk.Misc, row: int, column: int) -> tuple[StringVar, tk.OptionMenu]:
variable = StringVar(master)
# Get currently selected game
game_name = self.select_game_menu.get()
cheat_names = self.model_data[game_name]
if not list(cheat_names):
raise ValueError("No Cheatcode Data for GUI")
first_cheatcode_name = list(cheat_names)[0]
first_cheat_addresses = cheat_names[first_cheatcode_name]
variable.set(first_cheatcode_name)
names = []
# Adding '--Add New--' Option
names.append(self.add_cheatcode_option_string)
for cheat in cheat_names:
names.append( cheat )
O = tk.OptionMenu( self.root,
variable,
*names,
command=partial(lambda x: self.__update_cheatcode_entries_on_select())
)
O.grid(row=row, column=column)
return variable, O
# updates menu respective to currently selected game
def update_cheatcode_menu(self, option): # TODO: unused parameter option
# Update select_cheatcode for new game
grid_info = self.cheatcode_opt_menu.grid_info()
self.cheatcode_opt_menu.destroy()
self.select_cheatcode, self.cheatcode_opt_menu = self.create_cheatcode_option_menu(
master=self.root,
row=grid_info["row"],
column=grid_info["column"]
)
self.__update_cheatcode_entries_on_select()
def create_games_option_menu(self, row: int, column: int) -> tuple[StringVar, tk.OptionMenu]:
variable = StringVar(self.root)
gameCheats = self.model_data
variable.set(list(gameCheats)[0])
names = []
# Adding a 'new game' option
names.append(self.add_game_option_string)
for game in gameCheats:
names.append( game )
O = tk.OptionMenu( self.root,
variable,
*names,
command=self.handle_game_option_selection)
O.grid(row=row, column=column)
self.select_game_menu = variable
self.opt_menu = 0
self.select_cheatcode, self.cheatcode_opt_menu = self.create_cheatcode_option_menu(self.root, row=row+1, column=column)
return variable, O
def create_buttons(self, row: int, column: int):
frame = tk.Frame(self.root)
frame.grid(row=row, column=column)
show_all = tk.Button(frame,
text="Modify",
width=10,
command=partial(self.prepare_and_exec_callback, UserAction.MODIFY_DATA) )
show_all.pack(side=tk.TOP)
show_all.config(font=("Courier", 22))
end = tk.Button(frame,
text="QUIT",
width=25,
fg="red",
command=partial(self.prepare_and_exec_callback, UserAction.END_PROGRAM) )
end.pack(side=tk.BOTTOM)
ex = tk.Button(frame,
text="EXPORT",
width=25,
fg="blue",
command=partial(self.prepare_and_exec_callback, UserAction.EXPORT_ALL_DATA) )
ex.pack(side=tk.BOTTOM)
def init_for_interaction(self):
self.root = tk.Tk()
self.addresses_entries_frame = self.create_cheatcodes_frame(master=self.root, row=2, column=1)
# TODO maybe instead of OptionMenu for the Games use ListBox?
self.select_game_menu, self.opt_menu = self.create_games_option_menu(row=0, column=0)
self.create_buttons(row=2, column=0)
def interact(self):
self.init_for_interaction()
self.root.mainloop()
# Error Message Pop up
def error_msg_dialog(self):
messagebox.showerror("ERROR", "An error occurred:\n" +
str(self.error) +
"\n\n" +
"Traceback\n" +
str(self.traceback) )
# Adds confirmation dialog before every action
def confirmation_dialog(self, useraction: UserAction, data):
if self.TEST:
return True
return askyesno(title=str(useraction),
message="Do you want to perform action: \n\n" +
str(useraction) + "\n\n" +
"With data: \n\n" +
str(data))
def prepare_and_exec_callback(self, useraction: UserAction): # TODO: update signature to match UserInterface
self.state = useraction
# Check if data is needed for the useraction
if self.state in self.no_data_actions:
data = None
elif self.state == UserAction.ADD_NEW_GAME:
# create empty cheatcode list for the new game
game_name = self.select_game_menu.get()
data = {game_name: {'(m)':[]} }
self.callback( UserInput(UserAction.ADD_NEW_GAME, data) ) # TODO: resolve type missmatch (data: dict, expected: list[str])
else:
data = self.get_userdata_input()
if not self.confirmation_dialog(useraction, data):
return
try:
self.callback( UserInput(useraction, data) ) # TODO: resolve type missmatch (data: dict, expected: list[str])
except Exception as e:
self.traceback = traceback.format_exc()
self.error = e
self.state = UserAction.ERROR_MSG
self.update_gui()
return
self.update_gui()
###|--HUMBLE-OBJECT--|###
# TODO this should be outside of the humble object...
# TODO This function assumes that the game already exists -> this must be assured
# TODO: why does this function return a member of self?
# any caller may as well call self.model_data after calling self.merge_userinput_with_old_data
def __merge_userinput_with_old_data(self, game_name: str, cheatcode_name: str, new_addresses: list) -> dict:
cheat_names = self.model_data[game_name]
# Find changed cheat
cheat_names[cheatcode_name] = new_addresses
return cheat_names
# This function should return a dict object, structured like this:
# {String : {String : [HexStrings]}}
# {GameName : { Cheat00: [addr1, addr2, ...], Cheat01 : [...], ...} }
#
# Note: this function should give back a whole games+cheatcodes
def get_userdata_input(self) -> dict:
# Get game name from current OptionMenu Selection
game_name = self.select_game_menu.get()
cheatcodes = {}
new_addresses = []
# Note: Only the currently selected cheatcode and its addresses are pull from the GUI
cheatcode_entries = self.addresses_entries_frame.winfo_children()
selected_cheat_code_name = self.select_cheatcode.get()
for entry in cheatcode_entries:
if entry.get() != '':
new_addresses.append(entry.get())
cheatcodes = self.__merge_userinput_with_old_data(game_name,
selected_cheat_code_name,
new_addresses)
return {game_name : cheatcodes}
def clear_gui(self):
for cheatcode_entry in self.addresses_entries_frame.winfo_children():
cheatcode_entry.delete("0",tk.END)
cheatcode_entry.update()
def insert_into_gui(self, game_name: str, cheatcode_name: str, addresses: list) -> dict:
cheatcode_entries = self.addresses_entries_frame.winfo_children()
number_of_entries = len(cheatcode_entries)
for index, address in enumerate(addresses):
if number_of_entries > index:
cheatcode_entries[index].insert(tk.END, address)
cheatcode_entries[index].update()
else:
logging.error("Too many cheatcodes for display!")
raise IndexError()
return {"game_name" : game_name, "cheatcodes" : cheatcode_name, "addresses" : addresses}
###^--HUMBLE-OBJECT--^###
def update_gui(self):
mode = self.state
gui_data = []
# TODO: change to match clause if Python >= 3.10 is used
if mode == UserAction.END_PROGRAM:
self.root.quit()
elif mode == UserAction.SHOW_ALL_DATA_FROM_AR:
pass
elif mode == UserAction.PRINT_GAME:
for cheat in data.gameCheats: # TODO: data is undefined!
try:
print("Tk model.update_gui(): " + cheat.get_gameName())
except Exception as e:
print("Exception: " + e)
elif mode == UserAction.ERROR_MSG:
self.clear_gui()
self.error_msg_dialog()
pass
elif mode == UserAction.NO_ACTION:
pass
elif mode == UserAction.EXPORT_ALL_DATA:
pass
elif mode == UserAction.MODIFY_DATA:
self.model_data = json.loads(self.model.get_games_as_json())
elif mode == UserAction.ADD_NEW_GAME:
self.model_data = json.loads(self.model.get_games_as_json())
grid_info = self.opt_menu.grid_info()
self.cheatcode_opt_menu.destroy()
self.opt_menu.destroy()
self.select_game_menu, self.opt_menu = self.create_games_option_menu(row=grid_info["row"], column=grid_info["column"])
else:
print("View says: No Such UserAction")
raise ValueError