Compare commits
	
		
			16 Commits 
		
	
	
		
			b84e21a207
			...
			603acb60f6
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  Milan Suman | 603acb60f6 | |
|  Milan Suman | 82271182ba | |
|  Milan Suman | 74b554015c | |
|  Milan Suman | 93828490db | |
|  Milan Suman | b18d6849b0 | |
|  BodgeMaster | 39a5da7716 | |
|  BodgeMaster | e1fd26aa0f | |
|  BodgeMaster | 507747d5be | |
|  BodgeMaster | ae6fe2896c | |
|  BodgeMaster | 80e735937c | |
|  BodgeMaster | 02e095c321 | |
|  BodgeMaster | a33dbea7c8 | |
|  BodgeMaster | 3d62f1fabb | |
|  BodgeMaster | 6a1386f15c | |
|  BodgeMaster | 64fafe1d1e | |
|  Milan Suman | 0fc540e565 | 
							
								
								
									
										11
									
								
								config.py
								
								
								
								
							
							
						
						
									
										11
									
								
								config.py
								
								
								
								
							|  | @ -1,7 +1,8 @@ | |||
| import os, sys, json | ||||
| #TODO: port to QT once that little mainloop issue has been resolved... | ||||
| import tkinter as tk | ||||
| from tkinter import ttk | ||||
| import gui_helper, util | ||||
| import util | ||||
| 
 | ||||
| class Config: | ||||
|     def __init__(self, file_path, default_config): | ||||
|  | @ -18,19 +19,19 @@ class Config: | |||
|                 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_communication = util.Communication() | ||||
| 
 | ||||
|             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) | ||||
|             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="Quit", command=lambda: dialog_communication.send("create", False, additional_action=dialog.destroy)).grid(column=1, row=0) | ||||
|             dialog.resizable(0,0) | ||||
|             dialog.mainloop() | ||||
| 
 | ||||
|             if dialog_interaction_handler.get_result("create"): | ||||
|             if dialog_communication.get("create"): | ||||
|                 self.__current_config = default_config | ||||
|                 try: | ||||
|                     config_file = open(self.__file_path, "w") | ||||
|  |  | |||
|  | @ -1,16 +1,66 @@ | |||
| from PySide6 import QtCore, QtWidgets | ||||
| import sys | ||||
| 
 | ||||
| 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) | ||||
| from PySide6 import QtWidgets | ||||
| #import gui_helper | ||||
| import util | ||||
| 
 | ||||
| app = QtWidgets.QApplication([]) | ||||
| main_window = Window() | ||||
| main_window.resize(800, 600) | ||||
| main_window.show() | ||||
| 
 | ||||
| sys.exit(app.exec()) | ||||
| class Window(QtWidgets.QMainWindow): | ||||
|     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,56 +1,6 @@ | |||
| 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!") | ||||
|  |  | |||
							
								
								
									
										29
									
								
								main.py
								
								
								
								
							
							
						
						
									
										29
									
								
								main.py
								
								
								
								
							|  | @ -1,7 +1,6 @@ | |||
| #!/usr/bin/python3 | ||||
| import os | ||||
| import gui_helper | ||||
| import gui_handler | ||||
| import gui_helper, gui_handler | ||||
| from config import Config | ||||
| 
 | ||||
| ################################################################################ | ||||
|  | @ -9,9 +8,13 @@ from config import Config | |||
| ################################################################################ | ||||
| 
 | ||||
| default_configuration = { | ||||
|     "window geometry": (800, 600) | ||||
|     "window size": { | ||||
|         "x": 800, | ||||
|         "y": 600 | ||||
|     } | ||||
| } | ||||
| configuration_file_path = os.path.join(os.path.expanduser("~"), "concorde_ide_config.json") | ||||
| #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.json") | ||||
| 
 | ||||
| ################################################################################ | ||||
| # PROGRAM STARTUP | ||||
|  | @ -22,3 +25,21 @@ configuration = Config(configuration_file_path, default_configuration) | |||
| ################################################################################ | ||||
| # 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,3 +25,53 @@ def error(message, is_exception=True, handle_gracefully=True): | |||
|         pass | ||||
|     else: | ||||
|         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