Compare commits
	
		
			No commits in common. "b5124686bf6a8f29838ac4280f0e3321a9b084de" and "027a88342e70367055f8ac2566cd2c3bb30d6011" have entirely different histories. 
		
	
	
		
			b5124686bf
			...
			027a88342e
		
	
		|  | @ -1,3 +1 @@ | |||
| *.swp | ||||
| __pycache__ | ||||
| .spyproject | ||||
|  |  | |||
							
								
								
									
										73
									
								
								config.py
								
								
								
								
							
							
						
						
									
										73
									
								
								config.py
								
								
								
								
							|  | @ -1,73 +0,0 @@ | |||
| import os, sys, json | ||||
| import tkinter as tk | ||||
| from tkinter import ttk | ||||
| import gui_helper, util | ||||
| 
 | ||||
| class Config: | ||||
|     def __init__(self, file_path, default_config): | ||||
|         self.__file_path = file_path | ||||
|         self.__default_config = default_config | ||||
|         self.__current_config = {} | ||||
| 
 | ||||
|         if os.path.isfile(file_path): | ||||
|             try: | ||||
|                 config_file = open(self.__file_path, "r") | ||||
|                 self.__current_config = json.loads(config_file.read()) | ||||
|                 config_file.close() | ||||
|             except: | ||||
|                 util.error("An exception occurred while trying to load the configuration.", handle_gracefully=False) | ||||
|         else: | ||||
|             # config not found | ||||
|             dialog_interaction_handler = gui_helper.Window_Interaction_Handler() | ||||
| 
 | ||||
|             dialog = tk.Tk() | ||||
|             dialog.title("No configuration found") | ||||
|             ttk.Label(dialog, text="No configuration found!").pack() | ||||
|             buttons_frame = tk.Frame(dialog) | ||||
|             buttons_frame.pack() | ||||
|             ttk.Button(buttons_frame, text="Create", command=lambda: dialog_interaction_handler.interact("create", True, additional_action=dialog.destroy)).grid(column=0, row=0) | ||||
|             ttk.Button(buttons_frame, text="Quit", command=lambda: dialog_interaction_handler.interact("create", False, additional_action=dialog.destroy)).grid(column=1, row=0) | ||||
|             dialog.resizable(0,0) | ||||
|             dialog.mainloop() | ||||
| 
 | ||||
|             if dialog_interaction_handler.get_result("create"): | ||||
|                 self.__current_config = default_config | ||||
|                 try: | ||||
|                     config_file = open(self.__file_path, "w") | ||||
|                     config_file.write(json.dumps(self.__current_config)) | ||||
|                     config_file.close() | ||||
|                 except: | ||||
|                     util.warn("Failed to save initial config file.", is_exception=True) | ||||
|                     dialog = tk.Tk() | ||||
|                     dialog.title("Failed to save initial config file") | ||||
|                     ttk.Label(dialog, text="Failed to save the initial config file.\n" + | ||||
|                                     "The IDE can still run, but it is likely that all changes to the configuration will be lost where they would be saved otherwise.").pack() | ||||
|                     ttk.Button(dialog, text="Continue", command=dialog.destroy).pack() | ||||
|                     dialog.resizable(0,0) | ||||
|                     dialog.mainloop() | ||||
|             else: | ||||
|                 util.error("No config present and user chose not to create one. Exiting.", is_exception=False, handle_gracefully=True) | ||||
|                 # exit with success exit code anyway because this is not a program failure | ||||
|                 sys.exit(util.EXIT_SUCCESS) | ||||
| 
 | ||||
|     def get_configuration_value(self, key): | ||||
|         if not key in self.__current_config: | ||||
|             util.info("Requested configuration value for "+str(key)+" not in configuration. Loading from default configuration.") | ||||
|             try: | ||||
|                 self.__current_config[key] = self.__default_config[key] | ||||
|             except KeyError: | ||||
|                 util.error("Requested an invalid configuration key.") | ||||
|                 return None | ||||
|         return self.__current_config[key] | ||||
| 
 | ||||
|     def set_configuration_value(self, key, value, save_to_disk=True): | ||||
|         if not key in self.__current_config: | ||||
|             util.info("Writing configuration for previously unknown key "+str(key)+".") | ||||
|         self.__current_config[key]=value | ||||
|         if save_to_disk: | ||||
|             try: | ||||
|                 config_file = open(self.__file_path, "w") | ||||
|                 config_file.write(json.dumps(self.__current_config)) | ||||
|                 config_file.close() | ||||
|             except: | ||||
|                 util.error("Failed to save config file.") | ||||
							
								
								
									
										132
									
								
								gui_helper.py
								
								
								
								
							
							
						
						
									
										132
									
								
								gui_helper.py
								
								
								
								
							|  | @ -1,132 +0,0 @@ | |||
| import tkinter as tk | ||||
| import util | ||||
| 
 | ||||
| # easy way to get data out of window events | ||||
| class Window_Interaction_Handler: | ||||
|     # constructor | ||||
|     def __init__(self): | ||||
|         self.__window_interactions = {} | ||||
| 
 | ||||
|     # add a result for an interaction event, saves results in reverse order by default, can optionally call another function | ||||
|     def interact(self, name, result, reverse_order=True, additional_action=None, additional_action_parameters=()): | ||||
|         if name in self.__window_interactions: | ||||
|             if reverse_order: | ||||
|                 self.__window_interactions[name] = [result] + self.__window_interactions[name] | ||||
|             else: | ||||
|                 self.__window_interactions[name] = self.__window_interactions[name] + [result] | ||||
|         else: | ||||
|             self.__window_interactions[name] = [result] | ||||
| 
 | ||||
|         if not additional_action==None: | ||||
|             additional_action(*additional_action_parameters) | ||||
| 
 | ||||
|     # get first result for a given event from the list of results (newest (default) or oldest), removes the returned result from the list by default | ||||
|     def get_result(self, name, remove=True): | ||||
|         if name in self.__window_interactions and len(self.__window_interactions[name])>0: | ||||
|             if remove: | ||||
|                 result = self.__window_interactions[name].pop(0) | ||||
|                 if len(self.__window_interactions[name])==0: | ||||
|                     del self.__window_interactions[name] | ||||
|                 return result | ||||
|             else: | ||||
|                 return self.__window_interactions[name][0] | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     # get all results for a given event | ||||
|     def get_results(self, name, clear=False): | ||||
|         if name in self.__window_interactions and len(self.__window_interactions[name])>0: | ||||
|             results = self.__window_interactions[name] | ||||
|             if clear: | ||||
|                 del self.__window_interactions[name] | ||||
|             return results | ||||
| 
 | ||||
|     # clear results for a given event | ||||
|     def clear(self, name): | ||||
|         if name in self.__window_interactions: | ||||
|             del self.__window_interactions[name] | ||||
| 
 | ||||
|     # destructor | ||||
|     def __del__(self): | ||||
|         if len(self.__window_interactions)>0: | ||||
|             util.warn("__window_interactions not empty upon destruction of Window_Interaction_Handler:\n"+str(self.__window_interactions)) | ||||
| 
 | ||||
| 
 | ||||
| def not_implemented(): | ||||
|     util.warn("Not implemented!") | ||||
| 
 | ||||
| # format: | ||||
| #   "":{} -> menu or submenu | ||||
| #   "":function -> menu entry | ||||
| #   "":None -> disabled menu entry | ||||
| #   None:None -> separator | ||||
| # | ||||
| # Entries with ... at the end are supposed to open dialogs whereas entries without dots are supposed to take effect immediately | ||||
| menu_structure = { | ||||
|     "IDE": { | ||||
|         "Preferences...": not_implemented, | ||||
|         None: None, | ||||
|         "Quit": not_implemented | ||||
|     }, | ||||
|     "Project": { | ||||
|         "New": { | ||||
|             "No known project types": None | ||||
|         }, | ||||
|         "Open...": not_implemented, | ||||
|         "Close": { | ||||
|             "No open projects": None | ||||
|         }, | ||||
|         None: None, | ||||
|         "Preferences...": not_implemented, | ||||
|         "Search...": not_implemented, | ||||
|         "Build": not_implemented | ||||
|     }, | ||||
|     "File": { | ||||
|         "New...": not_implemented, | ||||
|         "Open...": not_implemented, | ||||
|         "Save": not_implemented, | ||||
|         "Close": not_implemented, | ||||
|         None: None, | ||||
|         "Rename...": not_implemented, | ||||
|         "Move...": not_implemented, | ||||
|         "View in File Explorer...": not_implemented | ||||
|     }, | ||||
|     "Edit": { | ||||
|         "Cut": not_implemented, | ||||
|         "Copy": not_implemented, | ||||
|         "Paste": not_implemented, | ||||
|         "Move code...": not_implemented, | ||||
|         None: None, | ||||
|         "Search and Replace...": not_implemented, | ||||
|         None: None, | ||||
|         "Format": not_implemented, | ||||
|         "Indent": not_implemented, | ||||
|         "Unindent": not_implemented, | ||||
|         "Toggle Comment": not_implemented | ||||
|     }, | ||||
|     "View": { | ||||
|         "Zoom in": not_implemented, | ||||
|         "Zoom out": not_implemented, | ||||
|         "Normal Size": not_implemented | ||||
|     }, | ||||
|     "Help": { | ||||
|         "Manual...": not_implemented, | ||||
|         "About IDE...": not_implemented, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| def build_menu(structure_dict, menu): | ||||
|     for entry in structure_dict: | ||||
|         if structure_dict[entry]==None: | ||||
|             if entry==None: | ||||
|                 menu.add_separator() | ||||
|             else: | ||||
|                 menu.add_command(label=entry) | ||||
|                 menu.entryconfig(entry, state="disabled") | ||||
|         if isinstance(structure_dict[entry], dict): | ||||
|             submenu = tk.Menu(menu, tearoff=False) | ||||
|             build_menu(structure_dict[entry], submenu) | ||||
|             menu.add_cascade(label=entry, menu=submenu) | ||||
|         if callable(structure_dict[entry]): | ||||
|             menu.add_command(label=entry, command=structure_dict[entry]) | ||||
| 
 | ||||
							
								
								
									
										238
									
								
								main.py
								
								
								
								
							
							
						
						
									
										238
									
								
								main.py
								
								
								
								
							|  | @ -1,24 +1,164 @@ | |||
| #!/usr/bin/python3 | ||||
| import os | ||||
| import tkinter as tk | ||||
| from tkinter import ttk | ||||
| import gui_helper | ||||
| from config import Config | ||||
| import sys, os, json, traceback | ||||
| 
 | ||||
| ################################################################################ | ||||
| # CONSTANTS | ||||
| # DEFINITIONS | ||||
| ################################################################################ | ||||
| 
 | ||||
| EXIT_SUCCESS=0 | ||||
| EXIT_ERROR=1 | ||||
| 
 | ||||
| default_configuration = { | ||||
|     "window geometry": "640x480" | ||||
| } | ||||
| configuration_file_path = os.path.join(os.path.expanduser("~"), "some_ide_config.json") | ||||
| # empty configuration by default because new values will be added while trying to load them | ||||
| # this allows the IDE to load old configuration files with missing keys | ||||
| configuration = {} | ||||
| home_directory = os.path.expanduser("~") | ||||
| config_file_path = os.path.join(home_directory, "some_ide_config.json") | ||||
| 
 | ||||
| def info(message): | ||||
|     # print info to sys.stderr because it isn’t really output, just debug information | ||||
|     print("INFO: "+str(message), file=sys.stderr) | ||||
|     traceback.print_stack() | ||||
| 
 | ||||
| def warn(message, is_exception=False): | ||||
|     print("WARNING: "+str(message), file=sys.stderr) | ||||
|     if is_exception: | ||||
|         traceback.print_exc() | ||||
|     else: | ||||
|         traceback.print_stack() | ||||
| 
 | ||||
| def error(message, is_exception=True, handle_gracefully=True): | ||||
|     print("ERROR: "+str(message), file=sys.stderr) | ||||
|     if is_exception: | ||||
|         traceback.print_exc() | ||||
|     else: | ||||
|         traceback.print_stack() | ||||
|     if handle_gracefully: | ||||
|         pass | ||||
|     else: | ||||
|         sys.exit(EXIT_ERROR) | ||||
| 
 | ||||
| def get_configuration_value(key): | ||||
|     if key in configuration: | ||||
|         pass | ||||
|     else: | ||||
|         info("Requested configuration value for "+str(key)+" not in configuration. Loading from default configuration.") | ||||
|         try: | ||||
|             configuration[key] = default_configuration[key] | ||||
|         except KeyError: | ||||
|             error("Requested an invalid configuration key.") | ||||
|             return None | ||||
|     return configuration[key] | ||||
| 
 | ||||
| def set_configuration_value(key, value, save_to_disk=True): | ||||
|     if not key in configuration: | ||||
|         info("Writing configuration for previously unknown key "+str(key)+".") | ||||
|     configuration[key]=value | ||||
|     try: | ||||
|         config_file = open(config_file_path, "w") | ||||
|         config_file.write(json.dumps(configuration)) | ||||
|         config_file.close() | ||||
|     except: | ||||
|         error("Failed to save config file.") | ||||
| 
 | ||||
| # easy way to get data out of window events | ||||
| class Window_Interaction_Handler: | ||||
|     # constructor | ||||
|     def __init__(self): | ||||
|         self.__window_interactions = {} | ||||
| 
 | ||||
|     # add a result for an interaction event, saves results in reverse order by default, can optionally call another function | ||||
|     def interact(self, name, result, reverse_order=True, additional_action=None, additional_action_parameters=()): | ||||
|         if name in self.__window_interactions: | ||||
|             if reverse_order: | ||||
|                 self.__window_interactions[name] = [result] + self.__window_interactions[name] | ||||
|             else: | ||||
|                 self.__window_interactions[name] = self.__window_interactions[name] + [result] | ||||
|         else: | ||||
|             self.__window_interactions[name] = [result] | ||||
| 
 | ||||
|         if not additional_action==None: | ||||
|             additional_action(*additional_action_parameters) | ||||
| 
 | ||||
|     # get first result for a given event from the list of results (newest (default) or oldest), removes the returned result from the list by default | ||||
|     def get_result(self, name, remove=True): | ||||
|         if name in self.__window_interactions and len(self.__window_interactions[name])>0: | ||||
|             if remove: | ||||
|                 result = self.__window_interactions[name].pop(0) | ||||
|                 if len(self.__window_interactions[name])==0: | ||||
|                     del self.__window_interactions[name] | ||||
|                 return result | ||||
|             else: | ||||
|                 return self.__window_interactions[name][0] | ||||
|         else: | ||||
|             return None | ||||
| 
 | ||||
|     # get all results for a given event | ||||
|     def get_results(self, name, clear=False): | ||||
|         if name in self.__window_interactions and len(self.__window_interactions[name])>0: | ||||
|             results = self.__window_interactions[name] | ||||
|             if clear: | ||||
|                 del self.__window_interactions[name] | ||||
|             return results | ||||
| 
 | ||||
|     # clear results for a given event | ||||
|     def clear(self, name): | ||||
|         if name in self.__window_interactions: | ||||
|             del self.__window_interactions[name] | ||||
| 
 | ||||
|     # destructor | ||||
|     def __del__(self): | ||||
|         if len(self.__window_interactions)>0: | ||||
|             warn("__window_interactions not empty upon destruction of Window_Interaction_Handler:\n"+str(self.__window_interactions)) | ||||
| 
 | ||||
| ################################################################################ | ||||
| # PROGRAM STARTUP | ||||
| ################################################################################ | ||||
| 
 | ||||
| configuration = Config(configuration_file_path, default_configuration) | ||||
| # read configuration | ||||
| if os.path.isfile(config_file_path): | ||||
|     try: | ||||
|         config_file = open(config_file_path, "r") | ||||
|         configuration = json.loads(config_file.read()) | ||||
|         config_file.close() | ||||
|     except Exception: | ||||
|         error("An exception occurred while trying to load the configuration.", handle_gracefully=False) | ||||
| else: | ||||
|     # config not found | ||||
|     dialog_interaction_handler = Window_Interaction_Handler() | ||||
| 
 | ||||
|     dialog = tk.Tk() | ||||
|     dialog.title("No configuration found") | ||||
|     ttk.Label(dialog, text="No configuration found!").pack() | ||||
|     buttons_frame = tk.Frame(dialog) | ||||
|     buttons_frame.pack() | ||||
|     ttk.Button(buttons_frame, text="Create", command=lambda: dialog_interaction_handler.interact("create", True, additional_action=dialog.destroy)).grid(column=0, row=0) | ||||
|     ttk.Button(buttons_frame, text="Quit", command=lambda: dialog_interaction_handler.interact("create", False, additional_action=dialog.destroy)).grid(column=1, row=0) | ||||
|     dialog.resizable(0,0) | ||||
|     dialog.mainloop() | ||||
| 
 | ||||
|     if dialog_interaction_handler.get_result("create"): | ||||
|         try: | ||||
|             config_file = open(config_file_path, "w") | ||||
|             config_file.write(json.dumps(default_configuration)) | ||||
|             config_file.close() | ||||
|         except: | ||||
|             warn("Failed to save initial config file.", is_exception=True) | ||||
|             dialog = tk.Tk() | ||||
|             dialog.title("Failed to save initial config file") | ||||
|             ttk.Label(dialog, text="Failed to save the initial config file.\n" + | ||||
|                             "The IDE can still start up, but it is likely that all changes to the configuration will be lost where they would be saved otherwise.").pack() | ||||
|             ttk.Button(dialog, text="Continue", command=dialog.destroy).pack() | ||||
|             dialog.resizable(0,0) | ||||
|             dialog.mainloop() | ||||
|     else: | ||||
|         error("No config present and user chose not to create one. Exiting.", is_exception=False, handle_gracefully=True) | ||||
|         # exit with success exit code anyway because this is not a program failure | ||||
|         sys.exit(EXIT_SUCCESS) | ||||
| 
 | ||||
| ################################################################################ | ||||
| # PROGRAM MAIN WINDOW | ||||
|  | @ -26,18 +166,94 @@ configuration = Config(configuration_file_path, default_configuration) | |||
| 
 | ||||
| main_window = tk.Tk() | ||||
| main_window.title("IDE") | ||||
| main_window.geometry(configuration.get_configuration_value("window geometry")) | ||||
| main_window.geometry(get_configuration_value("window geometry")) | ||||
| 
 | ||||
| def not_implemented(): | ||||
|     warn("Not implemented!") | ||||
| 
 | ||||
| # format: | ||||
| #   "":{} -> menu or submenu | ||||
| #   "":function -> menu entry | ||||
| #   "":None -> disabled menu entry | ||||
| #   None:None -> separator | ||||
| # | ||||
| # Entries with ... at the end are supposed to open dialogs whereas entries without dots are supposed to take effect immediately | ||||
| menu_structure = { | ||||
|     "IDE": { | ||||
|         "Preferences...": not_implemented, | ||||
|         "Quit": not_implemented | ||||
|     }, | ||||
|     "Project": { | ||||
|         "New": { | ||||
|             "No known project types": None | ||||
|         }, | ||||
|         "Open...": not_implemented, | ||||
|         "Close": { | ||||
|             "No open projects": None | ||||
|         }, | ||||
|         None: None, | ||||
|         "Preferences...": not_implemented, | ||||
|         "Search...": not_implemented, | ||||
|         "Build": not_implemented | ||||
|     }, | ||||
|     "File": { | ||||
|         "New...": not_implemented, | ||||
|         "Open...": not_implemented, | ||||
|         "Save": not_implemented, | ||||
|         "Close": not_implemented, | ||||
|         None: None, | ||||
|         "Rename...": not_implemented, | ||||
|         "Move...": not_implemented, | ||||
|         "View in File Explorer...": not_implemented | ||||
|     }, | ||||
|     "Edit": { | ||||
|         "Cut": not_implemented, | ||||
|         "Copy": not_implemented, | ||||
|         "Paste": not_implemented, | ||||
|         None: None, | ||||
|         "Search and Replace...": not_implemented, | ||||
|         None: None, | ||||
|         "Format": not_implemented, | ||||
|         "Indent": not_implemented, | ||||
|         "Unindent": not_implemented, | ||||
|         "Toggle Comment": not_implemented | ||||
|     }, | ||||
|     "View": { | ||||
|         "Zoom in": not_implemented, | ||||
|         "Zoom out": not_implemented, | ||||
|         "Normal Size": not_implemented | ||||
|     }, | ||||
|     "Help": { | ||||
|         "Manual...": not_implemented, | ||||
|         "About IDE...": not_implemented, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| menubar = None | ||||
| def rebuild_menu(structure_dict): | ||||
| def build_menu(structure_dict, menu): | ||||
|     for entry in structure_dict: | ||||
|         if structure_dict[entry]==None: | ||||
|             if entry==None: | ||||
|                 menu.add_separator() | ||||
|             else: | ||||
|                 menu.add_command(label=entry) | ||||
|                 menu.entryconfig(entry, state="disabled") | ||||
|         if isinstance(structure_dict[entry], dict): | ||||
|             submenu = tk.Menu(menu, tearoff=False) | ||||
|             build_menu(structure_dict[entry], submenu) | ||||
|             menu.add_cascade(label=entry, menu=submenu) | ||||
|         if callable(structure_dict[entry]): | ||||
|             menu.add_command(label=entry, command=structure_dict[entry]) | ||||
| 
 | ||||
| def rebuild_menu(): | ||||
|     menubar = tk.Menu(main_window) | ||||
|     gui_helper.build_menu(structure_dict, menubar) | ||||
|     build_menu(menu_structure, menubar) | ||||
|     main_window.config(menu=menubar) | ||||
| 
 | ||||
| rebuild_menu(gui_helper.menu_structure) | ||||
| rebuild_menu() | ||||
| 
 | ||||
| def handle_exit(): | ||||
|     configuration.set_configuration_value("window geometry", main_window.geometry()) | ||||
|     set_configuration_value("window geometry", main_window.geometry()) | ||||
|     main_window.destroy() | ||||
| 
 | ||||
| main_window.protocol("WM_DELETE_WINDOW", handle_exit) | ||||
|  |  | |||
							
								
								
									
										27
									
								
								util.py
								
								
								
								
							
							
						
						
									
										27
									
								
								util.py
								
								
								
								
							|  | @ -1,27 +0,0 @@ | |||
| import sys, traceback | ||||
| 
 | ||||
| EXIT_SUCCESS=0 | ||||
| EXIT_ERROR=1 | ||||
| 
 | ||||
| def info(message): | ||||
|     # print info to sys.stderr because it isn’t really output, just debug information | ||||
|     print("INFO: "+str(message), file=sys.stderr) | ||||
|     traceback.print_stack() | ||||
| 
 | ||||
| def warn(message, is_exception=False): | ||||
|     print("WARNING: "+str(message), file=sys.stderr) | ||||
|     if is_exception: | ||||
|         traceback.print_exc() | ||||
|     else: | ||||
|         traceback.print_stack() | ||||
| 
 | ||||
| def error(message, is_exception=True, handle_gracefully=True): | ||||
|     print("ERROR: "+str(message), file=sys.stderr) | ||||
|     if is_exception: | ||||
|         traceback.print_exc() | ||||
|     else: | ||||
|         traceback.print_stack() | ||||
|     if handle_gracefully: | ||||
|         pass | ||||
|     else: | ||||
|         sys.exit(EXIT_ERROR) | ||||
		Reference in New Issue