Compare commits
	
		
			No commits in common. "603acb60f6fcfd89b06147f45b33f334bc2bac67" and "b84e21a2071878537f8cc2aa26c54dd3a27606ee" have entirely different histories. 
		
	
	
		
			603acb60f6
			...
			b84e21a207
		
	
		
							
								
								
									
										11
									
								
								config.py
								
								
								
								
							
							
						
						
									
										11
									
								
								config.py
								
								
								
								
							|  | @ -1,8 +1,7 @@ | ||||||
| import os, sys, json | import os, sys, json | ||||||
| #TODO: port to QT once that little mainloop issue has been resolved... |  | ||||||
| import tkinter as tk | import tkinter as tk | ||||||
| from tkinter import ttk | from tkinter import ttk | ||||||
| import util | import gui_helper, util | ||||||
| 
 | 
 | ||||||
| class Config: | class Config: | ||||||
|     def __init__(self, file_path, default_config): |     def __init__(self, file_path, default_config): | ||||||
|  | @ -19,19 +18,19 @@ class Config: | ||||||
|                 util.error("An exception occurred while trying to load the configuration.", handle_gracefully=False) |                 util.error("An exception occurred while trying to load the configuration.", handle_gracefully=False) | ||||||
|         else: |         else: | ||||||
|             # config not found |             # config not found | ||||||
|             dialog_communication = util.Communication() |             dialog_interaction_handler = gui_helper.Window_Interaction_Handler() | ||||||
| 
 | 
 | ||||||
|             dialog = tk.Tk() |             dialog = tk.Tk() | ||||||
|             dialog.title("No configuration found") |             dialog.title("No configuration found") | ||||||
|             ttk.Label(dialog, text="No configuration found!").pack() |             ttk.Label(dialog, text="No configuration found!").pack() | ||||||
|             buttons_frame = tk.Frame(dialog) |             buttons_frame = tk.Frame(dialog) | ||||||
|             buttons_frame.pack() |             buttons_frame.pack() | ||||||
|             ttk.Button(buttons_frame, text="Create", command=lambda: dialog_communication.send("create", True, additional_action=dialog.destroy)).grid(column=0, row=0) |             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_communication.send("create", False, additional_action=dialog.destroy)).grid(column=1, 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.resizable(0,0) | ||||||
|             dialog.mainloop() |             dialog.mainloop() | ||||||
| 
 | 
 | ||||||
|             if dialog_communication.get("create"): |             if dialog_interaction_handler.get_result("create"): | ||||||
|                 self.__current_config = default_config |                 self.__current_config = default_config | ||||||
|                 try: |                 try: | ||||||
|                     config_file = open(self.__file_path, "w") |                     config_file = open(self.__file_path, "w") | ||||||
|  |  | ||||||
|  | @ -1,66 +1,16 @@ | ||||||
| from PySide6 import QtWidgets | from PySide6 import QtCore, QtWidgets | ||||||
| #import gui_helper | import sys | ||||||
| import util | 
 | ||||||
|  | class Window(QtWidgets.QWidget): | ||||||
|  |     def __init__(self): | ||||||
|  |         super().__init__() | ||||||
|  |         self.text = QtWidgets.QLabel("Hello World", alignment=QtCore.Qt.AlignCenter) | ||||||
|  |         self.layout = QtWidgets.QVBoxLayout(self) | ||||||
|  |         self.layout.addWidget(self.text) | ||||||
| 
 | 
 | ||||||
| app = QtWidgets.QApplication([]) | app = QtWidgets.QApplication([]) | ||||||
|  | main_window = Window() | ||||||
|  | main_window.resize(800, 600) | ||||||
|  | main_window.show() | ||||||
| 
 | 
 | ||||||
| class Window(QtWidgets.QMainWindow): | sys.exit(app.exec()) | ||||||
|     def __init__(self, size=(640, 480), title="Concorde"): |  | ||||||
|         super().__init__() |  | ||||||
|         self.setWindowTitle(title) |  | ||||||
|         self.resize(size[0], size[1]) |  | ||||||
|         self.show() |  | ||||||
| 
 |  | ||||||
|     def __del__(self): |  | ||||||
|         #TODO: whatever needs to be done here |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     def set_title(self, title): |  | ||||||
|         self.setWindowTitle(title) |  | ||||||
| 
 |  | ||||||
|     def get_size(self): |  | ||||||
|         #TODO: implement |  | ||||||
|         util.warn("Not implemented!") |  | ||||||
|         return None |  | ||||||
| 
 |  | ||||||
|     def set_size(self, size_x, size_y): |  | ||||||
|         self.resize(size_x, size_y) |  | ||||||
| 
 |  | ||||||
|     def update_menus(self, menu_dict): |  | ||||||
|         menu = self.menuBar() |  | ||||||
|         #Looping through entire menu_dict |  | ||||||
|         for topmenu, submenu in menu_dict.items(): |  | ||||||
|             #making top level menu |  | ||||||
|             menu_item = menu.addMenu(topmenu) |  | ||||||
| 
 |  | ||||||
|             #adding menu items (populating menu) |  | ||||||
|             for inner, data in submenu.items(): |  | ||||||
|                 #Adding submenu and populating it |  | ||||||
|                 if type(data) == dict: |  | ||||||
|                     sub = menu_item.addMenu(inner) |  | ||||||
|                     for label, func in data.items(): |  | ||||||
|                         sub.addAction(label) |  | ||||||
| 
 |  | ||||||
|                         if func == None: |  | ||||||
|                             sub.triggered.connect(lambda x: None) |  | ||||||
|                         else: |  | ||||||
|                             sub.triggered.connect(func) |  | ||||||
|                 #Adding separators |  | ||||||
|                 elif inner == None: |  | ||||||
|                     menu_item.addSeparator() |  | ||||||
|                 else:     |  | ||||||
|                     item = menu_item.addAction(inner) |  | ||||||
|                     item.triggered.connect(data) |  | ||||||
|      |  | ||||||
| 
 |  | ||||||
| #TODO: This needs to run in a thread but Qt really doesn't want it to. There are two ways around this: |  | ||||||
| #   - create the QtWidgets.QApplication inside a thread and run all QT stuff inside that thread |  | ||||||
| #   - make a generic wrapper for window mainloop that will always run in the main thread while the actual main control flow of the program gets moved to another thread |  | ||||||
| # There are some issues with these workarounds though; mainly that QT isn't thread safe. |  | ||||||
| # I really want to keep QT running in its own thread because I want to retain the ability to arbitrarily spawn and manipulate windows while other windows are running. |  | ||||||
| # Another issue that is probably easily worked around / fixed is that app.exec() will return once all running windows are closed. |  | ||||||
| # Idea for a workaround for both: |  | ||||||
| #   Maybe Qt has scheduled events in which case a scheduled polling event could run a function inside the Qt thread that fetches commands and executes them. |  | ||||||
| #   This could work by passing (lambda) functions through a Communication object. |  | ||||||
| def fixme_window_mainloop_workaround_to_just_get_a_window_started_really_should_not_be_implemented_this_way_for_reasons_stated_in_the_comment_above_the_definition_of_this_function(): |  | ||||||
|     app.exec() |  | ||||||
|  |  | ||||||
|  | @ -1,6 +1,56 @@ | ||||||
| import tkinter as tk | import tkinter as tk | ||||||
| import util | 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(): | def not_implemented(): | ||||||
|     util.warn("Not implemented!") |     util.warn("Not implemented!") | ||||||
|  |  | ||||||
							
								
								
									
										29
									
								
								main.py
								
								
								
								
							
							
						
						
									
										29
									
								
								main.py
								
								
								
								
							|  | @ -1,6 +1,7 @@ | ||||||
| #!/usr/bin/python3 | #!/usr/bin/python3 | ||||||
| import os | import os | ||||||
| import gui_helper, gui_handler | import gui_helper | ||||||
|  | import gui_handler | ||||||
| from config import Config | from config import Config | ||||||
| 
 | 
 | ||||||
| ################################################################################ | ################################################################################ | ||||||
|  | @ -8,13 +9,9 @@ from config import Config | ||||||
| ################################################################################ | ################################################################################ | ||||||
| 
 | 
 | ||||||
| default_configuration = { | default_configuration = { | ||||||
|     "window size": { |     "window geometry": (800, 600) | ||||||
|         "x": 800, |  | ||||||
|         "y": 600 |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| #TODO: make this a hidden file once development is far enough along that it doesn’t need to be recreated constantly | configuration_file_path = os.path.join(os.path.expanduser("~"), "concorde_ide_config.json") | ||||||
| configuration_file_path = os.path.join(os.path.expanduser("~"), "concorde.json") |  | ||||||
| 
 | 
 | ||||||
| ################################################################################ | ################################################################################ | ||||||
| # PROGRAM STARTUP | # PROGRAM STARTUP | ||||||
|  | @ -25,21 +22,3 @@ configuration = Config(configuration_file_path, default_configuration) | ||||||
| ################################################################################ | ################################################################################ | ||||||
| # PROGRAM MAIN WINDOW | # PROGRAM MAIN WINDOW | ||||||
| ################################################################################ | ################################################################################ | ||||||
| 
 |  | ||||||
| # It seems like opening multiple instances already works as intended |  | ||||||
| 
 |  | ||||||
| main_window = gui_handler.Window() |  | ||||||
| main_window2 = gui_handler.Window() |  | ||||||
| main_window.set_title("Concorde IDE") |  | ||||||
| main_window2.set_title("Another window") |  | ||||||
| main_window.set_size(configuration.get_configuration_value("window size")["x"], configuration.get_configuration_value("window size")["y"]) |  | ||||||
| main_window2.set_size(400, 500) |  | ||||||
| 
 |  | ||||||
| main_window.update_menus(gui_helper.menu_structure) |  | ||||||
| main_window2.update_menus(gui_helper.menu_structure) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| #TODO: get resolution of main window on exit and save it back to the configuration |  | ||||||
| #TODO: check if the GUI encountered an error in a toolkit agnostic way |  | ||||||
| 
 |  | ||||||
| gui_handler.fixme_window_mainloop_workaround_to_just_get_a_window_started_really_should_not_be_implemented_this_way_for_reasons_stated_in_the_comment_above_the_definition_of_this_function() |  | ||||||
|  |  | ||||||
							
								
								
									
										50
									
								
								util.py
								
								
								
								
							
							
						
						
									
										50
									
								
								util.py
								
								
								
								
							|  | @ -25,53 +25,3 @@ def error(message, is_exception=True, handle_gracefully=True): | ||||||
|         pass |         pass | ||||||
|     else: |     else: | ||||||
|         sys.exit(EXIT_ERROR) |         sys.exit(EXIT_ERROR) | ||||||
| 
 |  | ||||||
| # easy way to communicate across events |  | ||||||
| #TODO: make thread safe |  | ||||||
| class Communication: |  | ||||||
|     def __init__(self): |  | ||||||
|         self.__messages = {} |  | ||||||
| 
 |  | ||||||
|     # send a message tagged with name and containing content, adds to the beginning of the list of messages with the same tag by default, a function to run as an additional action can be provided |  | ||||||
|     def send(self, name, content, reverse_order=True, additional_action=None): |  | ||||||
|         if name in self.__messages: |  | ||||||
|             if reverse_order: |  | ||||||
|                 self.__messages[name] = [content] + self.__messages[name] |  | ||||||
|             else: |  | ||||||
|                 self.__messages[name] = self.__messages[name] + [content] |  | ||||||
|         else: |  | ||||||
|             self.__messages[name] = [content] |  | ||||||
|         if additional_action == None: |  | ||||||
|             pass |  | ||||||
|         else: |  | ||||||
|             additional_action() |  | ||||||
| 
 |  | ||||||
|     # get the content of the first message tagged with name, removes the returned message by default |  | ||||||
|     def get(self, name, remove=True): |  | ||||||
|         if name in self.__messages and len(self.__messages[name])>0: |  | ||||||
|             if remove: |  | ||||||
|                 content = self.__messages[name].pop(0) |  | ||||||
|                 if len(self.__messages[name])==0: |  | ||||||
|                     del self.__messages[name] |  | ||||||
|                 return content |  | ||||||
|             else: |  | ||||||
|                 return self.__messages[name][0] |  | ||||||
|         else: |  | ||||||
|             return None |  | ||||||
| 
 |  | ||||||
|     # get the contents for all messages tagged with name |  | ||||||
|     def get_all(self, name, clear=False): |  | ||||||
|         if name in self.__messages and len(self.__messages[name])>0: |  | ||||||
|             contents = self.__messages[name] |  | ||||||
|             if clear: |  | ||||||
|                 del self.__messages[name] |  | ||||||
|             return contents |  | ||||||
| 
 |  | ||||||
|     # deletes all messages tagged with name |  | ||||||
|     def clear(self, name): |  | ||||||
|         if name in self.__messages: |  | ||||||
|             del self.__messages[name] |  | ||||||
| 
 |  | ||||||
|     def __del__(self): |  | ||||||
|         if len(self.__messages)>0: |  | ||||||
|             warn("__messages not empty upon destruction of Communication object:\n"+str(self.__messages)) |  | ||||||
|  |  | ||||||
		Reference in New Issue